Robot Side

In the previous sections of this guide we have focused on the agent side and how to write TeleoR agents. In this section we look at the robot side and what the programmer is required to do in order to support the TeleoR agent. Most of the example programs come with a Python simulator that is behaving as both the environment and the robot side.

There are two major parts to consider when programming the robot side.

  1. Collecting sensor information, converting them into percepts, and sending the percepts to the agent as a peer-to-peer Pedro message.
  2. Turning robotic action messages from the agent into control actions, e.g. setting the speed of a motor or moving an arm.

We start by looking at percepts. How the programmer writes this depends on the percept interface being used. If the all interface is being used then, whenever the robot side decides to send percepts, it sends all the current percepts. If the update interface is being used the robot side sends percepts 'deltas' that we will discuss shortly.

Before getting into details we discuss the question of how often percepts should be sent independently of which interface is being used. One approach is to send percepts at a fixed time interval (as is done in the embedded agent case). Another approach is to only send percepts when “something interesting happens”. Of course what interesting means depends on the application but lets consider the bottle collector example and consider the robot moving towards the bottle, sending see percept messages that include direction and distance.

If we send see percepts at regular intervals and the robot is a long way from the bottle then, probably, the agent will keep moving at the same speed until the robot gets close. A better approach might be to only send percepts when the robot gets close. In the Python simulator we use a kind of logarithmic scale. When we cross the 32 distance unit boundary we send a percept, then when we cross the 16 unit boundary we send another percept and so on. This means that when the robot is far away we only send an occasional percept but as we get closer we send percepts more frequently.

If this approach makes sense in the application this can be more efficient than a time based version as the number of messages is typically lower and the agent is not updating the belief store (and therefore re-evaluating) unnecessarily often.

A variant of, or in conjunction with, this approach is to make the percepts as high-level as possible. This means the robot side does most of the translation from sensor information to percepts leaving the agent to work with high-level percepts that more directly match the guards of TeleoR rules. Naturally, the choice of what the percepts should be is an application-level decision that both the agent and robot side need to conform to.

When the agent starts the percept handling thread sends an initialise_ message to the robot side. When the robot side receives this message it will then have the address of that thread and it should use this address for sending percepts. The robot side should respond by sending an initial set of percepts consistent with the interface being used.

When the all interface is being used for a bottle collector we might expect the robot side to send a message like

[see(bottle, 0, centre)]

and then when the bottle is grabbed a message like

[see(bottle, 0, centre), holding()]

Note: all percepts need to be ground terms declared as percepts in the agent.

On the other hand, for the updates interface the percepts are wrapped with one of the following functors:

  1. r_ - the percept is to be remembered
  2. f_ - the percept is to be forgotten
  3. fa_ - all the percepts that match the percept pattern are to be forgotten
  4. u_ - the percept is to be updated.
For the first two, the enclosed percept must be ground and declared as a percept in the agent. The same is true for the fourth except some arguments may have a '!' wrapper as well. We discuss this shortly.

The enclosed percept in the third is a declared percept but may include variables in the arguments.

Note that, because the robot side is sending deltas, it needs to keep track of the current set of percepts so that it knows which wrapper to use for constructing the next list of wrapped percepts.

If we consider the bottle collector example where the robot is moving towards a bottle and assuming it sees the bottle on initialization. Then the initial percepts might be

[r_(see(bottle, 32, left)), r_(see(depot, 64, left))]

After a while we might get close enough to want to update the bottle distance and so we might want the percepts message to be

[u_(see(!(bottle), 16, centre))]

This is semantically equivalent to the message

[fa_(see(bottle, _, _)), r_(see(bottle, 16, centre))]

assuming we only produce a percept for the closest bottle.

The reason for wrapping the bottle argument is that we don't want to accedentially remove the depot percept.

Note that if there are no patterns that match an fa_ then nothing happens and so the initial percept message could also have been

[u_(see(bottle, 32, left)), u_(see(depot, 64, left))]

but would have taken slightly longer to process.

Generally speaking, however, using u_ rather than the semantically equivalent form produces a shorter message that is quicker to transmit and process.

It is possible for percepts to come from multiple sources but in that case the updates interface would be required and each source would need to be responsible for a fixed subset of percepts.

The other requirement of the robot side is to process robotic action messages from the agent such as the message

actions(collector, [move(4), turn(left)])

The message will be a list of ground terms of type robotic_action.

The programmer needs to decode the message string and translate the robotic actions into appropriate controls. Depending on the complexity of the message string the programmer might choose to use direct string processing or, for example, use one of the Pedro parsers supplied with the Pedro distribution. See, for example, the Python simulators or the ROS or MQTT C-level example interfaces in the examples folders.

Note that the robotic actions being sent are the ones that should now become active. This means that the robot side needs to keep track of the previous list of robotic actions.

Imagine that the above actions were previously sent and next the agent sends the action message

actions(collector, [move(2)])

This means that the turn(left) action needs to be stopped. Depending on the hardware being used, the move action needs to be modified or the old action stopped and the new action started.

In a multi-task application the robot side might need to keep track of the actions from each task so that the current set of actions can be correctly updated. The task argument of actions can be used to keep track of task spectific actions. If the robot side is interacting with several agents then the agent name in the sender address can be used for this purpose. In rare cases a combination of task and agent name might be required.

Some of the actions are durative (as above) and will continue unless stopped and others are terminating and will naturally terminate after a period of time such as an arm grasping an object. In the special case where the time to termination is effectively zero we call that action discreet. This distinction is application specific and the programmer needs to be careful to distinguish these cases. If an action is durative and is not present in the next set of actions it needs to be stopped. On the other hand if the action is terminating then it should only be stopped if it is still in progress.

Earlier we discussed using the tool robot_shell.py where the programmer can pretend to be the robot side so as to test TeleoR agent code. The dual of this is the supplied tool agent_shell.py where the programmer can pretend to be the agent. The programmer might find this tool useful when testing their implementation of the robot side. The programmer can send an initilise_ message and see the response and then send robotic actions and see if the robot responds correctly (and sends back appropriate percepts as the robot interacts with the environment).