# Query Annotator Testing and Validation This tutorial is designed to test the functionality of the `QueryAnnotator`, including goal acceptance, feedback processing, cancellation requests, and handling preemptions. The behavior tree pipeline ensures proper validation, execution flow, and interaction with the `Query Action Server`. ## Query Test Analysis Engine Structure The `AnalysisEngine` class (`robokudo/descriptors/analysis_engines/query_test.py`) is responsible for managing the behavior tree pipeline, ensuring the correct execution flow of query processing and validation. Below is a visual representation of the full behavior tree used in this pipeline: ``` RootNodeWithGUI ├── VisManager └── OPExperiments (WithMemory) ├── QueryAnnotator └── ConditionalSelector (NoMemory) ├── Invert Preempt Request │ └── ActionServerNoPreemptRequest └── TaskSequence (WithMemory) ├── CheckQueryType └── PrintNumbers ``` This diagram illustrates how the different components interact within the pipeline, including the conditional selector, query annotator, and task sequence. ## Task Execution Flow **Function:** Ensures that the query is validated before executing number processing. ``` TaskSequence (WithMemory) ├── CheckQueryType └── PrintNumbers ``` ```python # Define task sequence for execution task_sequence = Sequence(name="TaskSequence", memory=True) task_sequence.add_children([ CheckQueryType(), PrintNumbers() ]) ``` --- ### PrintNumbers Functionality The `PrintNumbers` annotator processes numbers from 1 to 100 while providing real-time feedback. As each number is processed, it is added to a feedback queue, which helps monitor progress. Once all numbers are processed, the final result is stored in the blackboard under `BBIdentifier.QUERY_ANSWER`. ### CheckQueryType Functionality The `CheckQueryType` annotator ensures that only queries of type `numbers` are processed. It retrieves the query from the CAS (Common Analysis Structure) and verifies its type. If the query type is not `numbers`, execution stops, and a warning is logged. If the query type matches, processing continues. ## Handling Preemption with Conditional Selector **Function:** Controls execution flow, ensuring tasks only run when no preemption request is detected. ``` ConditionalSelector (NoMemory) ├── Invert Preempt Request │ └── ActionServerNoPreemptRequest └── TaskSequence ``` ```python # Create a selector node to manage preemption conditional_selector = Selector(name="ConditionalSelector", memory=False) conditional_selector.add_children([ py_trees.decorators.Inverter( name="Invert Preempt Request", child=ActionServerNoPreemptRequest() ), task_sequence ]) ``` --- ### ActionServerNoPreemptRequest The `ActionServerNoPreemptRequest` behavior checks if a preemption request has been made on the blackboard. If detected, it acknowledges the request and returns `FAILURE`; otherwise, it returns `SUCCESS`. --- ### Selector The `Selector` node is responsible for decision-making by executing each of its child behaviors in sequence until one of them succeeds. If a child returns `SUCCESS` or `RUNNING`, the `Selector` does not check the remaining children. However, if all children return `FAILURE`, the `Selector` itself returns `FAILURE`. --- ### Inverter The `Inverter` in the code wraps `ActionServerNoPreemptRequest`, ensuring that a `FAILURE` (preemption detected) gets converted into `SUCCESS`, causing the `Selector` to stop and not proceed to `TaskSequence`. If no preemption is detected (`SUCCESS`), the `Inverter` converts it to `FAILURE`, which allows the `Selector` to execute `TaskSequence`. --- ### Memory Configuration (Handling Preemption) Memory configuration determines how nodes retain execution states between behavior tree ticks: - **memory=True**: The node remembers which child last succeeded or failed and resumes from that point on the next tick. - **memory=False**: The node re-evaluates all children from the beginning on each tick, ensuring up-to-date decisions. In the given implementation: - The **Selector** node is set to `memory=False`, meaning it always re-evaluates its children, ensuring that preemption (`ActionServerNoPreemptRequest`) is checked on every tick. - The **TaskSequence** node is set to `memory=True`, allowing it to resume from the last executed child without re-evaluating previous ones. --- ### ActionClient Functionality The `action_client.py` file (`robokudo/action_servers/action_client.py`) acts as a client for the **Query Action Server**, facilitating communication by sending and managing query requests. The `DebugActionClient` in this file sends a goal of type `numbers` to the action server, waits for a response, processes feedback, and handles goal cancellations dynamically. It ensures seamless interaction between external commands and the behavior tree pipeline.