Thursday, 20 November 2008

NXT Programming, lesson 10

Date: 21 11 2008
Duration of activity: 3 hours
Group members participating: all group members

1. The Goal

The goal of this lab session is to investigate how the implementation of a (namely, lejos's) behavior-based architecture works.

2. The Plan

  • Build and experiment with the idea of BumperCar

  • Investigate functions of the Arbitrator

  • Investigate alternative choices for Arbitrator

  • Discuss motivation functions

3. The Results

  • The first thing that was necessary to do in this lab session, was to prepare the robot for trying out the BumperCar program (which is included with lejos, in the samples/ directory). Towards that end, the robot was mounted with a touch sensor. Read more about that in 3.1. Robot construction.

  • As the robot was prepared for touching the environment, some experiments with the code were made and some discussions arose. Read more about that in 3.2. Investigations into the Arbitrator.

  • The idea of the Arbitrator that was discussed in 3.2. is just one way of doing things. There are alternative ways to implement Arbitrator. It is discussed in 3.3. Alternative ideas for Arbitrator.

  • Following the trail of discussion, 3.4. Motivation functions discusses the idea of motivation functions[1].

3.1. Robot construction

The construction of the robot is fairly simple. This is due to the fact that there aren't any observations of explicit robot behavior. Rather, the idea of this lab session is basically to figure out how and why the code works as it does. So now our robot is mounted with a touch sensor and it looks like this:

3.2. Investigations into the Arbitrator

  • When the touch sensor is being pressed.

  • When the touch sensor is pressed, the robot stops. 'takeControl' replies whether the behavior wants to be run, and in this case, the answer corresponds to the pressedness of the touch sensor. The Arbitrator iteratively asks if behaviors want to take control.

  • What happens if takeControl of DriveForward is called when HitWall is active?

  • The robot does not drive forward. The Behaviors of the system are prioritized, and the topmost is the touch behavior. It is always asked if it wants to take control and it always does, while the touch sensor is pressed. For this reason, 'drive forward' is never asked it is wants to take control.

  • Third behavior. Why is it that the exit behavior is not activated immediately? Why is it possible for HitWall to continue controlling the motors even after the suppress method is called?

  • If 'exit' was pressed with the source code given as it was, this far, it seems that the robot stops immediately. To investigate this, the back up time was set to 10 seconds to ensure HitWall to be active when 'escape' is pressed. This enables us to see that indeed the Exit behavior is not activated immediately.

    In the Arbitrator we have a for-loop which asks if HitWall wants to be active (by calling its takeControl predicate method), and the same is done for Exit behavior. It should be noted that the firstly activated behavior has to finish (i.e. return from action()), as there can only be one method running at a time in this setup. We cannot do anything to stop 'action' method (which again is a corollary to the fact that ordinary method invocations cannot be stopped in their tracks).

    HitWall continues controlling the motors even after the suppress method has been called from the Arbitrator. This is due to the fact that the action method has to finish (even if suppress is called) before anything else can happen, and this action method is rather long-running.

  • Fourth behavior. Explain.

  • We implemented PlaySound just like the other Behaviors. As intended, we put the PlaySound-behavior under HitWall-behavior, priority-wise, so that HitWall has both a behavior over it and under it, that can be activated by the button on the NXT unit. When only sticking to the theory one would think that the PlaySound-behavior would not be able to suppress the HitWall-behavior, but that the Exit-behavior would. But when we experimented with it, both behaviors seemed to be suppressed by HitWall. To find out why HitWall seemed to be able to suppress both the Exit- and PlaySound-behavior we had to look really deep into the code.

    The behavior-handler is implemented through a while(true)-loop, that constantly checks whether or not a high-ranking behavior wants access, over a lower ranking one.

    The decision about which behavior gets to run is done in some nested if-statments. The following code is the if-statments saying that, if i is a lower ranking behavior, it will yield. Or else it will suppress the current.

    if(currentBehavior >= i) // If higher level thread, wait to complete..
    while(!actionThread.done) {Thread.yield();}

    According to these lines of code, Exit should be able to suppress HitWall, but it doesn't.

    The reason for this, can be found further down in the class. The actual running of the behavior is done in another while(true)-loop, with takes whatever behavior is set as being the highest and runs its .action() method.

    public void action() {
    // Back up:
    try{Thread.sleep(1000);}catch(Exception e) {}
    // Rotate by causing only one wheel to stop:
    try{Thread.sleep(300);}catch(Exception e) {}

    And here is the problem: HitWall's action-method is a blocking method, that uses the .sleep() method while making the robot stop. But in between this time, the robot does not have the ability to switch behavior, and as a consequence one would have to hold down the Exit button for several seconds for it to be activated in the next deciding-behavior-cycle.

3.3. Alternative ideas for Arbitrator

The main issue with the current approach to the Arbitrator is that method invocations cannot be undone. Threads, however, usually can be forcefully destroyed (for example, they can in mainline Java), but this is not the case in lejos' Thread. This causes frustration, as embedded programs could easily benefit from this.

If Thread.destroy() had been defined, the best choice for implementing Behaviors would be to have each Behavior's action method be called in a seperate thread. In this way, execution could be stopped in general, since one could do a two-step approach: First interrupting the thread, and then, if it doesn't willingly terminate, forcefully kill it. The inteface for Behavior should, in this case, still require both an action and suppress method, since the Arbitrator would be required to do the thread's cleanup (its suppress invocation) for the thread, in case the Arbitrator kills off the thread forcefully.

Falling short of that functionality, the second-best approach is to encourage interfacers to write interrupt-aware action methods, and still do the first step of the above solution. In this way, conformant methods will be interruptable---albeit, conformance in this case means conformance to the encouragement. This is vastly inferior to having the possibility for forcefully killing the thread, since conformance can be destroyed by library calls: Calling a library routine which, itself, does a try { Thread.sleep(n)} catch (InterruptedException e){}, will void the InterruptedException that was intended for the interfacer.

A third---more esoteric---approach is to annotate the byte code inside the called action method with inserted if(Thread.interrupted()) return;-statements. This again could enforce the interruptability of the action method, but at a very high price. Reflection is always horribly slow, and must be even more so on the NXT.

As a variation of this proposal, one could choose to emulate the underlying JVM within the JVM, and interpret the called action method, rather than placing it on the JVM stack for execution.

Of course, one could also program one's code in a more natural language for an embedded platform. One that had interruptions enabled by default, perhaps. Or failing that, one---wearing one's firmware programmer hat---could implement such interruptability support into one's already terribly native-disfigured API.

3.4. Motivation functions

We ended up discussing how to use motivation functions to implement behavior in our robot. Instead of having ``takeControl'' return a binary answer on whether or not to use a behavior, let it return a value on how much the robots feels motivated for using this behavior. This way, one could make a more fine grained decision when deciding between behaviors---if nothing else, just implement it as if the answer was binary. When deciding upon with behavior to run, the one with the highest value wins. For actual deciding what value to return, the takeControl() would just have to be made more complex, and through measurements decide how motivated the robot is for this behavior. In the ``HitWall'' example, we could test whether or not we are in the middle of a HitWall-behavior, and therefore aren't that motivated to do further HitWall-behavior. This could be done by setting a boolean ``turning'' to true after we have driven backward and are about to turn. When takeControl() is called, we can then return different values through a statement if( turning ).

4. Conclusion

We built and experimented with a behavior-based architecture, and made a robot that showed the effects of the architecture. It didnt't do anything useful, and to some extent, this part of the exercise is a re-cap of the previous behavior-based-architecture lab session.

This exercise has been about understanding the given architecture, and we've done so. We've pondered both the questions in the lab assignment and we've been discussing issues that reach slightly outside the lab assigment's questions.

We've looked into possibilities for writing another implementation of Arbitrator, but our efforts conclude that the situation is pretty much helpless. And we've been discussing possibilities for making the architecture motivation-based, which also isn't strongly motivated by this assigment.


1.Thiemo Krink. Motivation Networks - A Biological Model for Autonomous Agent Control.

Our code for this week

No comments: