What is the default entry program for a state machine?

I have a simple state machine that acts differently depending on whether I define an empty entry program, or don't define an entry program. When I don't define an entry program (but it does have a run program), it appears to get locked into the default entry program and the machine stops responding to events. I know I'm doing _something_ wrong, I'm just not sure what. Sample code attached.

The unit test case is attempting to single step events through the state machine, and verifying the resulting state. I've seen the same locked-in-entry-program behaviour when using slave or periodic activities, when using execute() after each event on a slave activity or not using execute(), and in either mode of the state machine (reactive and automatic). The state machine currently has the entry programs of interest commented out.

Note that the ServoController task has the events goSafe(), goSoftware() and goHardware(), and contains the state machine given below.

Any help appreciated.
Stephen

NB ''TS_ASSERT()'' is CxxTest's equivalent of ''CPPUNIT_ASSERT()''

{{{
StateMachine ServoController
{
initial state SAFE
{
transition goSoftware() select SOFTWARE
transition goHardware() select HARDWARE
entry { do servocontroller.setSafe() }
}

state SOFTWARE
{
transition goSafe() select SAFE
transition goHardware() select HARDWARE
// entry { }
run { do servoController.softwareServo() }
}

state HARDWARE
{
transition goSafe() select SAFE
transition goSoftware() select SOFTWARE
// entry { }
run { do servoController.hardwareServo() }
}

final state ENDSAFE
{
entry { do servoController.setSafe() }
}
}

RootMachine ServoController servoController
}}}

** Unit test case of interest **
{{{
void test_States()
{
ServoController s("servoController");
MockupServoControllerPeer m("m", NUMDOF);
SlaveActivity as(s.engine());
SlaveActivity am(m.engine());
// PeriodicActivity as(RTT::OS::LowestPriority, 1, s.engine());
// PeriodicActivity am(RTT::OS::LowestPriority, 1, m.engine());

// set properties, connect ports/peers, load state machine, start activities
setupServoTest(s, m, as, am);

StateMachinePtr stateMachine = s.engine()->states()->getStateMachine("servoController");
TS_ASSERT(NULL != stateMachine);

// do one cycle
TS_ASSERT(am.execute());
TS_ASSERT(stateMachine->inInitialState());
TS_ASSERT(stateMachine->inStrictState("SAFE"));

// emit events, cycle, check for state changes
s.goSoftware();
TS_ASSERT(am.execute());
TS_ASSERT(stateMachine->inStrictState("SOFTWARE"));

s.goSafe();
TS_ASSERT(am.execute());
TS_ASSERT(stateMachine->inStrictState("SAFE"));

s.goHardware();
TS_ASSERT(am.execute());
TS_ASSERT(stateMachine->inStrictState("HARDWARE"));

s.goSoftware();
TS_ASSERT(am.execute());
### line 217: fails, the state machine is stuck in the entry program for SOFTWARE
TS_ASSERT(stateMachine->inStrictState("SOFTWARE"));
std::cout << stateMachine->getCurrentStateName() << std::endl;

s.goSafe();
TS_ASSERT(am.execute());
### line 222: fails and event goSafe() is ignored as still stuck in SOFTWARE entry program
TS_ASSERT(stateMachine->inStrictState("SAFE"));
std::cout << stateMachine->getCurrentStateName() << std::endl;

TS_ASSERT(as.stop());
TS_ASSERT(am.stop());
}

}}}

Possibly "operator error"

With "as.execute()" instead of "am.execute()" above, it works as anticipated.

So as a matter of interest, why was it partially working when the peer was being executed but not the task containing the state machine?

Sorry for the trouble
S

Possibly "operator error"

On Thursday 24 April 2008 17:39:22 snrkiwi wrote:
> With "as.execute()" instead of "am.execute()" above, it works as
> anticipated.
>
> So as a matter of interest, why was it partially working when the peer was
> being executed but not the task containing the state machine?

First of all: could you run the program again with export ORO_LOGLEVEL=7 and
send over the orocos.log file ? There might be a bug with event handler
creation...

The partial execution could only be the case when the event handler was
executed synchronously, which in turn could/should only happen when both
event sender and event receiver share the same event processor, which in turn
should only happen when event is defined in the same taskcontext as the state
machine, which is in turn plain wrong. I start to believe that all events in
scripting must be handled asynchronously, no matter where the event is
registered (because anyone/thread can fire it).

If you see 'Creating Synchronous handler for ...' in the debug logs, we're in
trouble.

Peter

Possibly "operator error"

>The partial execution could only be the case when the event handler was
>executed synchronously, which in turn could/should only happen when both
>event sender and event receiver share the same event processor, which in turn
>should only happen when event is defined in the same taskcontext as the state
>machine, which is in turn plain wrong. I start to believe that all events in
>scripting must be handled asynchronously, no matter where the event is
>registered (because anyone/thread can fire it).

Ok, so if I understand your "plain wrong" bit above, it seems that events should be defined in a different task, and then this task's script should react to those events (as listed in Section 4.4.6 in the Component Builder's Manual online). I think I've done this in the example below, but it doesn't run. See error at end ... the script doesn't know about the "other" task that has the events. Is something more than connectPorts() needed here?

.... ok after writing the above, I figured it out, but thought I'd still post as this isn't mentioned anywhere I can see in the documentation, even though it does make sense ...

In the code below, the script is loaded _before_ the peers are connected, whereas it must be loaded _after_ the peers are connected so that the script parser knows about the other tasks. I had tried many combinations of moving lines around, but not that particular one. The RTT unit test cases don't have a complete example like this - might it be useful to put one in the tests or into the documentation?

Cheers
S

class ArmControl : public TaskContext
{
private:
Method d_logState;

public:
ArmControl(string name):
TaskContext(name),
d_logState("logState", &ArmControl::logState, this)
{
methods()->addMethod(
&d_logState, "Log state", "State", "Name of state");
}

class User : public TaskContext
{
private:
Event< void(void) > doSAFE;
Event< void(void) > doJJ;
Event< void(void) > doRR;

public:
User(string name):
TaskContext(name),
doSAFE("doSAFE"),
doJJ("doJJ"),
doRR("doRR")
{
events()->addEvent(&doSAFE, "Do SAFE");
events()->addEvent(&doJJ, "Do JJ");
events()->addEvent(&doRR, "Do RR");
}

StateMachine ArmControl
{
initial state safe
{
entry { do armControl.logState("safe") }
transition user.doJJ() select jj ***** line 8 *****
transition user.doRR() select rr
}

int ORO_main(int arc, char* argv[])
{
log().setLogLevel( Logger::Info );
ArmControl armControl("armControl");
PeriodicActivity armControl_activity(
ORO_SCHED_RT, OS::HighestPriority, 1.0 / 1, armControl.engine());

User user("user");
PeriodicActivity user_activity(
ORO_SCHED_RT, OS::LowestPriority, 1.0 / 1, user.engine());

armControl.scripting()->loadStateMachines("fsm.osd");

armControl_activity.start();
user_activity.start();

if (!connectPorts(&user, &armControl))
{
log(Error) << "Unable connect ports" << endlog();
}
if (!connectPeers(&user, &armControl))
{
log(Error) << "Unable connect peers" << endlog();
}

// activate a state machine :
armControl.engine()->states()->getStateMachine("armControl")->activate();
// start a state machine (automatic mode) :
armControl.engine()->states()->getStateMachine("armControl")->start();

0.006 [ Info ][ParserScriptingAccess::loadStateMachine] Parsing file fsm.osd
0.013 [ ERROR ][ParserScriptingAccess::loadStateMachine] fsm.osd :Parse error at line 8: Syntactic error: From TaskContext 'armControl': Task 'armControl' has no task or object 'user'.

Possibly "operator error"

On Thursday 24 April 2008 23:03:26 S Roderick wrote:
> >The partial execution could only be the case when the event handler was
> >executed synchronously, which in turn could/should only happen when both
> >event sender and event receiver share the same event processor, which in
> > turn should only happen when event is defined in the same taskcontext as
> > the state machine, which is in turn plain wrong. I start to believe that
> > all events in scripting must be handled asynchronously, no matter where
> > the event is registered (because anyone/thread can fire it).
>
> Ok, so if I understand your "plain wrong" bit above, it seems that events
> should be defined in a different task, and then this task's script should
> react to those events (as listed in Section 4.4.6 in the Component
> Builder's Manual online). I think I've done this in the example below, but
> it doesn't run. See error at end ... the script doesn't know about the
> "other" task that has the events. Is something more than connectPorts()
> needed here?
>
> .... ok after writing the above, I figured it out, but thought I'd still
> post as this isn't mentioned anywhere I can see in the documentation, even
> though it does make sense ...
>
> In the code below, the script is loaded _before_ the peers are connected,
> whereas it must be loaded _after_ the peers are connected so that the
> script parser knows about the other tasks. I had tried many combinations of
> moving lines around, but not that particular one. The RTT unit test cases
> don't have a complete example like this - might it be useful to put one in
> the tests or into the documentation?

Just to be sure... you did look at the example code at
?

I guess we need a tutorial to explain the basic concepts we are using (ports
vs peers, events vs methods and commands etc.) and the 'execution model'.
We're so used to these concepts that we don't question their 'logicness'
anymore. Don't hesistate to post when you have errors that you don't
understand...

Peter

Possibly "operator error"

>> In the code below, the script is loaded _before_ the peers are
>> connected,
>> whereas it must be loaded _after_ the peers are connected so that the
>> script parser knows about the other tasks. I had tried many
>> combinations of
>> moving lines around, but not that particular one. The RTT unit test
>> cases
>> don't have a complete example like this - might it be useful to put
>> one in
>> the tests or into the documentation?
>
> Just to be sure... you did look at the example code at
> ?

I am ashamed to admit, no. I did download it but because it wasn't
inside the rtt or ocl dir's, I never saw it and just kept hunting
through the unit test cases for help. Totally my fault, sorry! They
would have helped ... :-(

> I guess we need a tutorial to explain the basic concepts we are
> using (ports
> vs peers, events vs methods and commands etc.) and the 'execution
> model'.
> We're so used to these concepts that we don't question their
> 'logicness'
> anymore. Don't hesistate to post when you have errors that you don't
> understand...

Overall I think the documentation is pretty damn good! You should all
be proud of what you've achieved. IMHO, this is one of the most
advanced, well documented, and capable robot control frameworks/
systems I've seen.

There are some places that I think could use more/better explanation
- what actually hapens within the 'execution model'. How are programs,
events, commands, state machines, and updateHook's processed, in what
order (if it matters), and what are the interactions between them.
- what actually happens during a non-transition cycle of a state
machine. It is very hard to understand when run() is called and when
handle() is called. I still don't get it ...
- I think methods vs commands is well explained, as are the basic
concepts of ports vs peers, events, etc. It's the _connections_ and
_interactions_ between these items that is unclear (and hard to write
well, I'll admit).
- the online documentation lacks complete examples, and gives just
enough to almost figure things out. If you have a bonehead like me who
doesn't have or realise that complete examples are available, they're
stuck without enough information to connect all the dots. Perhaps a
section in the documentation with at least one complete example, and
referencing the examples that are available and indicating that you
need to download them separately.
- Some of the example code doesn't build (motion_control). It uses
classes that don't seem to exist (eg I can't find CartesiaSensor
anywhere). Is this not built very often (and hence it isn't known that
it's broken)?
- personally I found the tutorial to be nearly useless, because I'm
interested from a programmatic point of view. How do I translate
system XYZ from a design to actual code? I found that the tutorial
doesn't help here. It does help once you get an idea of what the
TaskBrowser is actually doing for you, but by then you're already well
down the track.
- I and a couple of others found that we had to read and re-read the
Component Builder's Manual a couple of times before we got the general
approach. It's a tough read, but perhaps simply because there's so
much in there (which is good!). This makes me think that the biggest
piece of documentation missing is the "why would you use Orocos?" or
"what benefit is this tool?". The answer to this is written in there,
but it's really not clear and I think that is what takes several
readings to sort out. On the first reading you're kind of like "huh,
that's an interesting way of doing things but I need to know more ..."
and it takes a couple more before your brain starts to connect the
dots and go "OH! That allows me to do that ... and that .... oh, _now_
I get it!". We need to shorten that transition with a real high level
"this is what Orocos does, this is how it compares to traditional
approaches, and this is how this is better". For instance, the benefit
of writing interpreted programs and having them interact with a state
machine, which is in turn interacting with tasks, is only evident from
reading the examples (I don't _think_ I saw it directly referenced
anywhere). The benefit of this is the ability to very quickly develop
high-level control programs without having to write much code, ie
shortening the development process. Great for those of us regularly
doing demo's for VIPs, etc ....

Though YMMV, I hope this helps ...
S

Possibly "operator error"

On Thursday, April 24, 2008, at 03:58PM, "Peter Soetens"

<peter [dot] soetens [..] ...> wrote:
>On Thursday 24 April 2008 17:39:22 snrkiwi wrote:
>> With "as.execute()" instead of "am.execute()" above, it works as
>> anticipated.
>>
>> So as a matter of interest, why was it partially working when the peer was
>> being executed but not the task containing the state machine?
>
>First of all: could you run the program again with export ORO_LOGLEVEL=7 and
>send over the orocos.log file ? There might be a bug with event handler
>creation...
>
>The partial execution could only be the case when the event handler was
>executed synchronously, which in turn could/should only happen when both
>event sender and event receiver share the same event processor, which in turn
>should only happen when event is defined in the same taskcontext as the state
>machine, which is in turn plain wrong. I start to believe that all events in
>scripting must be handled asynchronously, no matter where the event is
>registered (because anyone/thread can fire it).
>
>If you see 'Creating Synchronous handler for ...' in the debug logs, we're in
>trouble.

Perhaps we're in trouble, but perhaps not .... :-( ... see below (with ORO_LOGLEVEL=7). I've also attached the init code that wasn't sent previously, just in case.

Note that the events of interest are defined in the same class (derived from task context) that the state machine runs in. Is this incorrect? We noticed that events are supposed to be "emitted" _from_ tasks, but we had awful trouble trying to get the state machine to connect up to another task's events. So we modeled the events within this class and figured other tasks can call them to emit the events, thereby affecting the state machine. I'm starting to get the feeling that this is backwards ... ???

Cheers
S

0.000 [ Info ][Logger] Successfully extracted environment variable ORO_LOGLEVEL
0.000 [ Info ][Logger] OROCOS version '1.4.99' compiled with GCC 4.1.3. Running in GNU/Linux.
0.000 [ Info ][Logger] Orocos Logging Activated at level : [ Debug ] ( 6 )
0.000 [ Info ][Logger] Reference System Time is : 1209068147105752718 ticks ( 1.20907e+09 seconds ).
0.000 [ Info ][Logger] Logging is relative to this time.
0.001 [ Debug ][Logger] MainThread started.
0.001 [ Debug ][Logger] Starting StartStopManager.
0.001 [ Info ][Toolkit] Loading Tool RealTime.
0.001 [ Debug ][Toolkit] Registered Type 'int' to the Orocos Type System.
0.001 [ Debug ][Toolkit] Registered Type 'uint' to the Orocos Type System.
0.001 [ Debug ][Toolkit] Registered Type 'double' to the Orocos Type System.
0.001 [ Debug ][Toolkit] Registered Type 'bool' to the Orocos Type System.
0.001 [ Debug ][Toolkit] Registered Type 'PropertyBag' to the Orocos Type System.
0.001 [ Debug ][Toolkit] Registered Type 'float' to the Orocos Type System.
0.002 [ Debug ][Toolkit] Registered Type 'char' to the Orocos Type System.
0.002 [ Debug ][Toolkit] Registered Type 'array' to the Orocos Type System.
0.002 [ Debug ][Toolkit] Registered Type 'string' to the Orocos Type System.
0.002 [ Debug ][Logger] Registered Type 'stringList' to the Orocos Type System.
0.002 [ Debug ][ExecutionEngine] Creating ExecutionEngine for servoController
0.004 [ Debug ][Logger] Event Created with name : goSafe
0.004 [ Debug ][Logger] Event Created with name : goSoftware
0.004 [ Debug ][Logger] Event Created with name : goHardware
0.004 [ Debug ][ExecutionEngine] Creating ExecutionEngine for m
0.005 [ Info ][ExecutionEngine::setActivity] servoController is not periodic.
0.005 [ Info ][ExecutionEngine::setActivity] m is not periodic.
0.005 [ Info ][ParserScriptingAccess::loadStateMachine] Parsing file data/orocos/servocontroller.osd
0.009 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSoftware'.
0.010 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goHardware'.
0.011 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.012 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSafe'.
0.012 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goHardware'.
0.013 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.013 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSafe'.
0.014 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSoftware'.
0.014 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.015 [ Debug ][Logger] Program entry loaded in ProgramProcessor.
0.016 [ Debug ][Logger] Creating an instance of ServoController
0.016 [ Debug ][Logger] Program entry loaded in ProgramProcessor.
0.016 [ Debug ][Logger] Program entry loaded in ProgramProcessor.
0.017 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.017 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.017 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.017 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.017 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.017 [ Debug ][Logger] Program run loaded in ProgramProcessor.
0.017 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSoftware'.
0.017 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goHardware'.
0.017 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSafe'.
0.017 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goHardware'.
0.017 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSafe'.
0.017 [ Debug ][StateMachine::createEventTransition] Creating Synchronous handler for 'goSoftware'.
0.018 [ Info ][Logger] Loading StateMachine 'servoController'
0.018 [ Info ][servoController] Connected Port theta_c to peer Task m.
0.018 [ Info ][servoController] Connected Port theta_req to peer Task m.
0.018 [ Info ][servoController] Connected Port torque_req to peer Task m.
0.018 [ Debug ][servoController] Configuring
0.018 [ Debug ][servoController] Number axes = 7
0.018 [ Debug ][servoController] Starting
0.019 [ Debug ][Logger] ServoController::softwareServo
0.019 [ Debug ][Logger] ServoController::setSafe
0.019 [ Info ][ExecutionEngine::setActivity] m is disconnected from its activity.
0.019 [ Info ][ExecutionEngine::setActivity] servoController is disconnected from its activity.
0.019 [ Debug ][~ExecutionEngine] Destroying ExecutionEngine of m
0.019 [ Debug ][~ExecutionEngine] Destroying ExecutionEngine of servoController
0.019 [ Info ][~ExecutionEngine] StateMachineProcessor unloads StateMachine servoController...
0.020 [ Debug ][Logger] Stopping StartStopManager.
0.020 [ Debug ][Logger] Stopping MainThread.
0.020 [ Info ][Logger] Orocos Logging Deactivated.

void
setupServoTest(ServoController& s,
MockupServoControllerPeer& m,
ActivityInterface& as,
ActivityInterface& am)
{
PropertyBag p;
Property nAxes("naxes","naxes", NUMDOF);
p.add(&nAxes);
TS_ASSERT(refreshProperties(*s.properties(), p, true));

TS_ASSERT_THROWS_NOTHING(
s.scripting()->loadStateMachines("data/orocos/servocontroller.osd"));

// connect, configure and start
TS_ASSERT(connectPeers(&s, &m));
TS_ASSERT(connectPorts(&s, &m));
TS_ASSERT(s.configure());
TS_ASSERT(m.configure());
TS_ASSERT(am.start());
TS_ASSERT(as.start());

// activate state machine and put in automatic mode
StateMachinePtr stateMachine = s.engine()->states()->getStateMachine("servoController");
TS_ASSERT(NULL != stateMachine);
TS_ASSERT(stateMachine->activate());
TS_ASSERT(stateMachine->start());
TS_ASSERT(stateMachine->inInitialState());
}

class ServoController : public RTT::TaskContext
{

public:
/// Event to go to the safe state (ie no servo loop)
RTT::Event< void(void) > goSafe;
/// Event to start using the software servo loop
RTT::Event< void(void) > goSoftware;
/// Event to start using the hardware servo loop
RTT::Event< void(void) > goHardware;
protected:
/// Set all outputs to their safe states
RTT::Method d_setSafe_method;
/// Do one cycle of the software servo
RTT::Method d_softwareServo_method;
/// Do one cycle of the hardware servo
RTT::Method d_hardwareServo_method;

Possibly "operator error"

On Thursday 24 April 2008 22:24:20 S Roderick wrote:
> On Thursday, April 24, 2008, at 03:58PM, "Peter Soetens"

<peter [dot] soetens [..] ...> wrote:
> >On Thursday 24 April 2008 17:39:22 snrkiwi wrote:
> >> With "as.execute()" instead of "am.execute()" above, it works as
> >> anticipated.
> >>
> >> So as a matter of interest, why was it partially working when the peer
> >> was being executed but not the task containing the state machine?
> >
> >First of all: could you run the program again with export ORO_LOGLEVEL=7
> > and send over the orocos.log file ? There might be a bug with event
> > handler creation...
> >
> >The partial execution could only be the case when the event handler was
> >executed synchronously, which in turn could/should only happen when both
> >event sender and event receiver share the same event processor, which in
> > turn should only happen when event is defined in the same taskcontext as
> > the state machine, which is in turn plain wrong. I start to believe that
> > all events in scripting must be handled asynchronously, no matter where
> > the event is registered (because anyone/thread can fire it).
> >
> >If you see 'Creating Synchronous handler for ...' in the debug logs, we're
> > in trouble.
>
> Perhaps we're in trouble, but perhaps not .... :-( ... see below (with
> ORO_LOGLEVEL=7). I've also attached the init code that wasn't sent
> previously, just in case.

Yes, it's a bug. The causing code should be simply removed from
StateMachine.cpp.

>
> Note that the events of interest are defined in the same class (derived
> from task context) that the state machine runs in. Is this incorrect? We
> noticed that events are supposed to be "emitted" _from_ tasks, but we had
> awful trouble trying to get the state machine to connect up to another
> task's events. So we modeled the events within this class and figured other
> tasks can call them to emit the events, thereby affecting the state
> machine. I'm starting to get the feeling that this is backwards ... ???

It's your choice where to define the events. I would define them in the same
task as where you want to react to them. This would mean that your
statemachine can be loaded, even if no peers are (yet) present. But I guess
you'll always have some more 'global' events as well in a kind of 'global'
task, which is connected to all tasks. The application architecture is up to
you.

Peter