-
Notifications
You must be signed in to change notification settings - Fork 12
Designators
#In a nutshell
A designator encapsulates the process of finding some value that is needed at runtime. This process is called resolving. For example, there can be a designator that resolves to some entity. ''How'' this is resolved is encapsulated in the .resolve-method of a designator and is thus hidden as an implementation detail.
A designator may encapsulate a query to the reasoner, a query to Ed or a custom process or function etc.
Amigo executes RoboCup challenges using a script, usually in the form of a Smach state machine. Smach is used to structure the steps to achieve some goal. We want to be able to parametrize with what these steps are performed, e.g. ''what'' to pick up and ''where'' to transport it to. Also, if Amigo needs to remember something, e.g. "I tried going to some item but it was unreachable", then that fact needs to be remembered. Before the use of designators, such facts were asserted and later queried to the reasoner. This eventually became an enormous mess which was incomprehensible.
So, another way to let Amigo remember stuff and to parametrize behavior was conceived: Designators.
One (type of) designator can use ED, another the reasoner/WIRE, yet another can be hardcoded and yet another can just be set like a variable by another state. This allows to also encapsulate filtering, sorting, applying some function, getting some attribute etc. Basically, any value that is not known at write-time of an executive and needs to be known at runtime (and can be determined from the environment or some runtime process). A designator can be seen as being somewhat like a lazy variable, that is only calculated when needed.
A RoboCup challenge or any other application is a task description or recipe. Like a recipe, it tells the robot what to do with what .e.g. grab a pill bottle using one of your arms. Robot_smach_states provide a library of reusable actions (e.g. Grab) and describe ''how'' to do something. Designators provide the other half of the recipe: ''with what'' to do something. Designators thus allow to separate the how of a task from the 'with what' of a task. The how is reusable through robot_smach_states between RoboCup challenges/applications, while the application-specific parts are captured in designators.
#Introduction A designator can resolve to any python object, depending on how its .resolve()-method is implemented. Thus, it can resolve to an Ed Entity, a PointStamped, a string, an Arm of the robot etc. and a Python list just as well.
For example:
- An object with Entity ID "AC23DE74BF75"
- "An entity with type ... within some range of point ...",
- "The arm holding some entity",
- "Grandmothers glasses" (if you write a proper ED query),
- "An arm available for grasping" or
- a string indicating the class of some desired object, like "Coke"
Some properties of designator can be tricky:
- For for some designators, this will always resolve to the same Python-object, e.g. a string like "AC23DE74BF75"
- For other types of designator, it may resolve to a different value everytime, e.g. when the designator designates the location of some moving object. A LockingDesignator can be used to lock a designator to a constant value.
- When you can create a designator and resolve it a few lines down in the same function. In that case, there really is no point in using designators. They encapsulate a process, just like a normal function.
Designators are important to get right and it must this be easy to get them right. Some help is available in this regard:
- There are type checks
- You can visualize the dataflow through your designators and see where information is coming from and who is using it.
- Writing can be made explictly, so that a VariableDesignator cannot just be written to. You need to specify who can write what designator, so its easier to see what state manipulates data and which only reads/resolves it.
All designators have a resolve_type that specifies what type the thing is that they resolve to. E.g. a EdEntityDesignator's resolve_type is an Ed Entity (ed.msg.EntityInfo). The robot_smach_states check that the designators they are constructed with are the correct types by using check_resolve_type(designator, expected_type)
All standard designators specify their resolve_types where possible. In others, or custom ones, you need to specify the resolve_type in the constructor.
By calling
analyse_designators(statemachine, "challenge_name")
you can generate an image showing the data flow graph of your application/RoboCup challenge. To make this easier to read, you can give every designator a name via its constructor.
In order to give you a better insight in the data flow in your application and to make debugging easier, it's now mandatory to declare which smach states are allowed to write and change a VariableDesignator. To write a designator:
- Pass the designator to the smach state postfixed with .writeable: variable_designator.writeable
- In the smach state's .execute-method, use variable_designator.write(value)
- In the smach state's init-method, you can use is_writeable(var_designator) to check whether its writeable.
-
Make EntityByIdDesignator(id=...).DONE -
Make standard ReasonedEntityDesignator for queries that result in an IDDONE -
Give all designators locking-capabilities so you don't need to compose it into a LockingDesignator.DONE -
.lock() can mean different things for different designator types: if you know the ID of an entity, resolving it should update it 3D position etc.DONE - Maybe there are too many standard designators that try to be too generic. Settle to a few often used cases and make custom designators for the rest.
- Split up the functionality of the EdEntityDesignator.
That means a EntityByIdDesignator that only takes robot and id as parameter.Another version takes a type. For other cases, create a custom designator. -
If we promote custom designators, make the typecheck on the resolve-method as well.DONE - Specifying resolve_type by inheritance, eg class SomeDesignator(EntityDesignator). Then you only have to implement the resolve-call.
#Class hierarch The library defines a couple of standard designators:
- Designator: simply returns a predefined value that defaults to None
- Requires setting resolve_type when constructing
- VariableDesignator: any user of this designator can set the designators value and can be used to pass around data.
- Requires setting resolve_type when constructing
- LockingDesignator: after calling .lock(), locks a designator to the same value until it is .unlock()-ed again. This should alleviate the problem of a designator resolving to different objects when you don't expect it.
- Automatically finds the resolve_type of the designator it wraps
- AttrDesignator: Some designator types wrap other designators, like AttrDesignator. It returns some attribute of of whatever the wrapped designator resolved to
- Requires setting resolve_type when constructing
- FuncDesignator: Apply a function to the resolution of another, wrapped, designator
- Requires setting resolve_type when constructing
- DeferToRuntime: Execute a function at runtime. You can use all variables etc in the current scope.
- Requires setting resolve_type when constructing
With these, some possibilities are present:
With a VariableDesignator, you can call
variablewriter_of_vardesignator.write(...)
and later
result = vardesignator.resolve()
It basically provides a pipe between Smach states at runtime, just like a normal variable would for normal code.
There are also some designators to work with Ed, Arms and other robot-specific things:
- EdEntityDesignator: Get an entity from Ed by a query. The arguments of this query (id, type, center_point) can be designators themselves for flexibility. An EdEntityDesignator can also take a list of criteria functions. Each criteria function is given each candidate entity and must return if the entity matches some criterium. A single entity matching all criteria is returned, if any.
- ArmDesignator: resolves to an arm of the robot
- UnoccupiedArmDesignator: Resolves to an arm that is not holding anything. Useful when passed to Grab as the arm-argument
- ArmHoldingEntityDesignator: Resolves to an arm that ''is'' holding some entity, as indicated by a EdEntityDesignator. Useful in combination with Place if you want to place that Entity somewhere.
Some designator types, like AttrDesignator and FuncDesignator wrap another designator and call a function on the result of a designator or get an attribute of a designator's result. This can be used for casting values from one type to another, as this also simply executing a function. It is also used to get the ID of an ED entity: by default, an EdEntityByQueryDesignator resolves to an actual entity, but some states only take an Entity's ID. So you wrap the first designator in an AttrDesignator that gets the entity's ID-attribute.
You can see designators as functions whose execution is deferred to later (and are executed whenever needed). Function calls can be nested, e.g.
max(int(raw_input("enter an integer: ")), float(raw_input("Enter a float: ")))
Some designators can also be nested, such as LockingDesignator, EdEntityDesignator, UnoccupiedArmDesignator and ArmHoldingEntityDesignator. They take other designators as arguments (similar to the wrapping ones above) but use the outcome of the referenced designators to find another value related to the outcome of the referenced one. Just as you would with nested function calls.
For example, the ArmHoldingEntityDesignator takes a designator that resolves to some entity. Then, the arms of the robot are searched to find an arm that is occupied by that entity. That arm is then the outcome of the ArmHoldingEntityDesignator.
item = EdEntityDesignator(robot, id="1234567abcd")
arm_to_place_item_with = ArmHoldingEntityDesignator(robot.arms, item)
Collection, such as lists and sets, are also just values. A VariableDesignator has a .current-attribute that can be used:
designator.current.append(...)
designator.current.next()
=Combinations (TODO, but please see [Designators can be combined:
- If we first make a designator A for some object to grab, we can then define a designator B that designates the arm that grabbed the object.
- Define a designator that is close to/on top of/inside an entity from some other designator.
In a RoboCup challenge, you sometimes want to iterate over a bunch of entities, e.g. pick and place all items on top of some table or take a look at all persons in a room. You can use and EdEntityDesignator to express that you only want persons:
current_human = EdEntityDesignator(type="human")
But, you only want to look at each person only once. The current best solution for this is to add each entity to a list of processed entities. The designator for current_human then must check whether the entity is finds is not in that list, which is an extra criterium to the entity: it must be a human AND is must not be already processed. So, we extend the designator with a criterium:
processed_entities = []([#Including]])=)
current_human = EdEntityDesignator(type="human", criteria_funcs=[entity: not entity in processed_entities](lambda))
Of course, the human-entity must be put on that list after taking a look at it:
#start statemachine, work with the entity.
#After placing or whatever with the entity: add it to some list to ignore it later on
...
processed_entities += [#Wrapped in a smach.CBState that follows a state that uses the current_human designator
...
Previously, we used the reasoner to assert which items were unreachable, visited etc. You can keep this all in Python code now and keep track of the logic a bit better I hope.
These use-cases of designators are relatively common and something must be done to make it easier to do that use-case.
- Select some EdEntity in a some smach state and use that EdEntity later on (Some sort of locking to an ID but keep updating other info)
- Define or ask for an object type in some state and later use that type to select an EdEntity.
- Iterate over a collection of items, exclude the already processed items from that list (Done, can be done with the EdEntityCollectionDesignator and a filter function)
- Lock to some entity, but keep updating the entity's other data when you resolve it again. (i.e. a smarter LockingDesignator specifically for EdEntities, basically the same as the first use-case)
- Lock and unlock a designator (Maybe make a with-context that inserts the lock/unlock-states)
- Add the resolution of a designator to a list
- Go to an EdEntity with some predefined ID (this already pretty simple and well understood, using EdEntityDesignator(robot, id=...))
- Pass a variable from one state to another (This is already simple enough with a VariableDesignator(resolve_type=))
- Resolve to an unexplored entity, that is not on some list of unexplored things and sort by closest one.
- Make a fake Entity to go to some PoseStamped
Any object in Python can be a designator, as long as it has a .resolve()-method that returns something. In a challenge, you can create a designator specifically for that challenge and even use variables from its http://stackoverflow.com/questions/291978/short-description-of-python-scoping-rules lexical scope.
#Syntactic sugar (TODO Designators could be context managers:
with specific_object = collection_designator.refine({"Class":"coke"}):
GrabMachine(specific_object)
with next_object = collection_designator.next():
GrabMachine(next_object)
Where ordered_drink is a designator containing some query.
DONE, see subclasses of ArmDesignator: When we want to determine which arm to use at execution time, AMIGO needs to know the state of each arm. This is a start for a more semantic self-image of the robot. When the state of an arm changes, Amigo can say ouch!
Split up te designators module into core, util, ed, arm etc.-
Make clear when writing a state macine which write to a designator and which states read from it.Currently a GitHub pull request.
Designators allow to separate the how and what of executives. 'How' are the actual smach states, while designators take care of the 'what' part. Smach states can be drawn as a graph of execution, while designators frm some sort of dataflow-graph.