1.1.4. Task Arbitration

The task arbitration service can be used to trigger and arbitrate various simple or short lived subroutines of the apartment, aka tasks. Tasks can be executed by sending TaskState objects to the following scope: SCOPE_TASK=/coordination/request/task/<ID>, or by using the Button Infrastructure. The service internally uses the Allocation Service for the arbitration of parallel invocations and only accepts and executes a task if it can allocate all needed resources.

The life-cycle of a single task looks like the following. The initiating component, called submitter, thereby usually only triggers the INITIATED, ABORT, and UPDATE states. The handler uses ACCEPTED or REJECTED to signal if it is able to process the task and informs if it finished successful with a COMPLETED state or sends FAILED in an error scenario.

Task state pattern (c.f.  `TaskState`_)

The task state pattern that is monitored by the coordination (c.f. TaskState)

Tasks can be processed (handled) by an arbitrary component in the apartment that communicates via rsb. Each invokation of the task arbitration service maps an incoming request from the submitter to either another TaskState, an rpc call, or a simple informer message on the handler side. In all cases, tasks are requested via a TaskState object with INITIATED as its state and SUBMITTER as its origin. The arbitration service’s answer depends on the handler’s implementation. In case the handler component supports task states, communication is redirected bidirectionally. Otherwise, the task is accepted immediately by the arbitration service. If the handler is an informer, it is also completed directly afterwards. In case of an rpc, it is completed after the method call returns or fails if the method times out or produces an exception.

If resources are lost, the task is set to aborted in both the submitter and the handler. The following diagram depicts how tasks are arbitrated between the submitter and handler using the Allocation Service:

Call graph of tasks with resources that are available or unavailable, that are being superseded or expired.

Call graph of tasks following the TaskState protocol using resources that are available or unavailable, that are being superseded or expired. Each task is mapped transparently to a ResourceAllocation.

1.1.4.1. Available tasks

Currently, the following tasks are available:

Task Purpose Involved components
lighton light on everywhere for 10 minutes (allocates light resources) location-light
lightoff light off everywhere for 10 minutes (allocates light resources) location-light
autolight resume automatic light (only frees resources) -
identifyperson identifies a person via face recognition face id
elan displays an ELAN screenshot at the BFT displays
nextinscene skips to the next song in the current scene’s playlist Guiro (Radio/Media Player)
stopaudio stops audio playback Guiro (Radio/Media Player)
legacy (inactive)  
debug displays the desktop at the BFT to make grafana visible displays
greet greeting (currently only triggers an utterance in the dialog) Dialog Manager (DM)
goodbye goodbye (currently only triggers an utterance in the dialog) Dialog Manager (DM)
introduction introduction (greet & light, list, learnperson) Dialog Manager (DM)
list scenario listing (short explanation) Dialog Manager (DM)
explain scenario explanation (long/with commands) Dialog Manager (DM)

1.1.4.2. Task specification

All available tasks are read from a configuration file that resides inside the main coordination repository. If you want to add a new task, you have to create a new Ini file containing an optional [resources] and a single [remote] section. These files are evaluated at runtime and trigger the remote after the resources have been allocated sucessfully. Remotes can be specified as inofmer, task, or rpc and given a payload. The following code-block illustrates an example task with the default resources that are allocated and an rpc handler. Please refer to Resource specification for an overview of allowed values for the [resources] section.

[resources]
; these are the default values for task resources
policy=MAXIMUM
priority=NORMAL
initiator=HUMAN
duration=10000
ids=/coordination/request/task/<ID>
description="task: <ID>"

[remote]
; these are the default values for delay/timeout
delay=0
timeout=5000
; example values below
type=rpc
scope=/home/living/hometheater/display/tv/ctrl/showImage
payload='file:///vol/csra/releases/xenial/lsp-csra-rc/share/adhoc_res/elan.png'

A [remote] section specifies an interaction with another component via TaskState, remote procedure call (rpc), or simple message (informer). Types, scopes, and payloads are mandatory for a remote seciton. The scope field has to follow the RSB Scope definition. The type field can be any of task, rpc, or informer. The payload are evaluated as numbers, booleans, or RST data types. Additionally, all these parameters can be overridden by using a Dictionary as the TaskState payload when requesting a scene. delay and timeout fields are optional, default to 0 and 5000 and are evaluated as millisecond values.

In contrast to tasks, in scenes multiple remote sections can be specified.

1.1.4.3. Examples

The following example works with pure RSB/RST and do not need an additional library.

// Import the following:
import com.google.protobuf.ByteString;
import rsb.*;
import rst.communicationpatterns.TaskStateType.TaskState;

1.1.4.3.1. Initiate a task (enable the automatic light)

 // RSB Informer
 Informer<TaskState> informer = Factory.getInstance().
     createInformer("/coordination/request/task/autolight");
 informer.activate();

 // create a task state
 TaskState.Builder taskBuilder = TaskState.newBuilder()
     .setOrigin(TaskState.Origin.SUBMITTER)
     .setState(TaskState.State.INITIATED)
     .setSerial(1)
     .setPayload(ByteString.EMPTY)
     .setWireSchema(ByteString.copyFromUtf8("utf-8-string"));

 // send the task state
 informer.send(taskBuilder.build());

1.1.4.3.2. Check whether the task has been carried out successfully

 // RSB Listener
 Listener listener = Factory.getInstance().createListener("/coordination/request/task/autolight");
 listener.activate();

 // Add a local event handler
 listener.addHandler(new Handler(){
     @Override
     public void internalNotify(Event event) {

         // Only handle task state types
         if (event.getData() instanceof TaskState) {
             TaskState taskState = (TaskState) event.getData();

             // Only regard tasks that originate from the handler (i.e., the arbitration)
             if (taskState.getOrigin().equals(TaskState.Origin.HANDLER)) {

                 // Investigate payload: print out content if string
                 if (taskState.getWireSchema().equals(ByteString.copyFromUtf8("utf-8-string"))) {
                     String payloadStr = (String) taskState.getPayload().toStringUtf8();
                     System.out.println("message content: " + payloadStr);
                 } else {
                     System.out.println("other message content");
                 }

                 // Investigate state
                 switch(taskState.getState()){
                     case REJECTED:
                         System.out.println("Task execution refused.");
                         break;
                     case ABORTED:
                         System.out.println("Task aborted (externally).");
                         break;
                     case FAILED:
                         System.out.println("Task failed (component).");
                         break;
                     case COMPLETED:
                         System.out.println("Task successful.");
                         break;
                 }
             }
         }
     }
 }, true);