wiki:BehaviorEngine
Last modified 3 years ago Last modified on 13.10.2015 12:11:31

Overview

The Behavior Engine (BE) is a layer for development, execution, and monitoring of robot behaviors.

Information on the ROS port of the BE can be found at http://www.ros.org/wiki/behavior_engine.

The general idea is to have a fawkes thread maintain a user-defined hybrid state machine which is implemented in a LUA framework. The current state is maintained in the fawkes thread, which periodically re-evaluates transition conditions and advances the state accordingly. The BE is split into a purely reactive layer (the Skiller) and a higher-level deliberative layer (the Agent), which is meant to implement long-shot and strategic decisions.For a more in-depth conceptual description of the BE see this presentation.

The luadoc for the entire BE is currently in the GIT branch containing the new skiller backported from ROS.

Papers

Developing A Behavior Engine for the Fawkes Robot-Control Software and its Adaptation to the Humanoid Platform Nao
Diploma thesis describing the original Behavior Engine for Fawkes and its design principles.
A Lua-based Behavior Engine for Controlling the Humanoid Robot Nao
Conference paper describing the BE more concise, but less practical.
HERB 2.0: Lessons Learned from Developing a Mobile Manipulator for the Home
Journal article describing the bi-manual mobile manipulator HERB 2.0. Section V describes the BE ported to ROS.

Skills

Skills are aggregated into a common Skillspace, which needs to be defined in the /skiller/skillspace setting, for example:

INSERT INTO "config" VALUES('/skiller/skillspace','string','test','Skill space');

The skillspace is then initialized as a LUA module with the regular search order, usually in src/lua/skills. Since a skillspace will normally be comprised of multiple skills, it's common practice to put the skillspace module into a folder, e.g. src/lua/skills/test/, containing an init.lua which pulls in the other skill modules, e.g.:

-- init.lua: require all skill modules. Make sure the load order reflects their dependencies.
require('testskill')
require('some_skill_depending_on_testskill')
-- [...]

The load order of the single skills is important and must reflect the skills' dependencies!

Each skill module requires the following boilerplate code which defines dependencies and other required data structures:

module(..., skillenv.module_init)
name               = "fetch_puck"
fsm                = SkillHSM:new{name=name, start="INITIAL_STATE", debug=true}
depends_skills     = { "some_skill", "some_other_skill" -- [...]
}
depends_interfaces = {
   {v = "skill_var_holding_interface", type="IfaceClassName", id="iface_config_id"}
   -- [...]
}

documentation      = [==[This is displayed in the skillgui.
Explain parameter names and their meaning.]==]

skillenv.skill_module(_M)

The Hybrid State Machine

The control flow of both skills and agents is implemented as a hybrid state machine. Each state is represented by a LUA object that is created by the skiller environment. States are defined using the fsm:define_states() method:

fsm:define_states{ export_to=_M,
   { "INITIAL_STATE", JumpState },
   { "EXEC_SUBSKILL", SkillJumpState, skills={{some_subskill}},
     final_to="CHECK_RESULT", fail_to="RETRY" },
   { "CHECK_RESULT", JumpState },
   { "RETRY", JumpState },
}

See SkillJumpState for details on required and optional properties and on the usage of subskills in general.

Note that when using the luaagent? the naming and internal mechanism of using skills changes. Instead of SkillJumpState one needs to write AgentSkillExecJumpState?. The main difference is that when skills are used in the skiller the code of the subskill is directly embedded, while the luaagent? communicates to the subskill via the BlackBoard. This also implies that the luaagent? cannot access the subskill directly (e.g. self.skills.some_variable).

The HSM now contains the states INITIAL_STATE, EXEC_SUBSKILL, CHECK_RESULT, RETRY plus the predefined state FAILED. Each of these state objects can implement well-known functions that are executed in specific situations:

State function Executed...
STATE_NAME:init() once when state is entered
STATE_NAME:loop() periodically while STATE is active
STATE_NAME:exit() once when state is left

However, the HSM is not complete since the only transitions defined lead from EXEC_SUBSKILL to CHECK_RESULT and RETRY. The transitions from any JumpState? are defined with the fsm:add_transitions() method:

fsm:add_transitions{
   {"INITIAL_STATE","EXEC_SUBSKILL",cond=check_input},
   {"CHECK_RESULT", "RETRY", cond=result_not_ok},
   {"CHECK_RESULT", "FINAL", cond=result_ok},
   {"RETRY", "FAILED", cond=timeout_reached},
   {"RETRY", "EXEC_SUBSKILL", cond=timeout_not_reached}
}

Now the HSM is almost complete. The only thing missing are the condition functions referenced in cond=.... Recall that LUA has higher-order functions which means that a function name is just variable referencing the function implementation, and as such can be passed around and even reassigned.

Conditions

There are two ways of defining a condition for a possible transition, which makes the HSM decide which transition to follow.

  • Directly assign a function reference (no parentheses!!). For an example, see above.
  • Assign a lua expression as a string. Therefore all needed variables and functions have to be passed to fsm:define_states within the named parameter "closure" as a hash table. Example:
    fsm:define_states{ export_to=_M,
       closure = {dog=cat}, --
       {"SOME_STATE", JumpState}
    }
    
    fsm:add_transitions{
       {"SOME_STATE", "FAILED", cond="dog == \"Red Baron\""},
       {"SOME_STATE", "FINAL", cond="dog == \"Snoopy\" or dog == \"Pluto\"}, 
    }