This repository is archived, as we are expanding the scope of the repository beyond the forked project. A new stand-alone repository will replace this repository.
ReFRESH (Reconfigurable Framework for Real-time Embedded Software and Hardware)
This is a ROS integration of the ReFRESH framework as a middleware for real-time self-adaptation of robotic systems on both behavior and composition, involving both software and hardware.
Based on BehaviorTree.CPP and Actionlib, this ROS package extends the run-time self-adaptation capability of ROS. The framework of ReFrESH is referred to (Cui et. al, 2015), where a Evaluator and Estimator are added to the functional component (Executor) to achieve run-time self-awareness. A decider reconfigures the system on-the-fly based on the EV and ES outputs. This package further extends the work of ReFRESH by decoupling behavior and logic concerns. Hence, it consists of High-Level (behavior) adaptation modules powered by a behavior tree, and Low-Level (component and logic) adaptation modules integrated with the native ROS API.
Infrastructure of Real-Time Self-Adaptation
The package provides three types of Action nodes, and two types of Control nodes. The Control nodes include a Decider node and a Module node. State machines of the two nodes are as followed:
-
Decider Node (
ReFRESH_Decider
)Allow connection to multiple children, ranked in ascending order of weighted costs and (if the costs are the same) the connection order for execution. It allows connection to both
ReFRESH_Module
and non-ReFRESH_Module
(in which costs will be1.0-FLT_EPSILON
forSUCCESS
or1.0
forFAILURE
, to guarantee the priority ofReFRESH_Module
s).N.B. The reconfigure logic currently adopts a weighted sum of normalized performance and resource costs as the ranking metric, and adopts the candidate with minimum weighted metric and feasible costs (0 <= {resource, performance} cost < 1).
stateDiagram-v2
[*] --> IDLE
IDLE --> RUNNING\nReconfigure\nchild←altChild : tick()
RUNNING\nchild.tick() --> SUCCESS : child.status()==SUCCESS
RUNNING\nReconfigure\nchild←altChild --> FAILURE : ∀ child.status()==FAILURE\n|| isDepleated()
RUNNING\nchild.tick() --> RUNNING\nchild.tick() : tick()&&\nchild.status()==RUNNING
RUNNING\nchild.tick() --> RUNNING\nReconfigure\nchild←altChild : tick()&&\nchild.status()==FAILURE
RUNNING\nReconfigure\nchild←altChild --> RUNNING\nchild.tick() : otherwise
RUNNING\nchild.tick() --> [*] : onHalted()
SUCCESS --> [*]
FAILURE --> [*]
-
Module Node (
ReFRESH_Module
)Connects to maximum of three, and minimum of one child (children), in the order of Executor, Evaluator, and Estimator. If no Estimator is provided, a constant
SUCCESS
is used as the belief. If no Evaluator is provided, a constantFAILURE
is used during initialtick()
to initiate the Executor. Then the resultant state of the Executor is used as the state of the Evaluator.N.B. The Evaluator is tested on the first call to see if it is synchronous or asynchronous (whether it has the state
RUNNING
). If the Evaluator is synchrounous, it would not receivetick()
again unless the Executor has finished.
stateDiagram-v2
[*] --> IDLE
IDLE --> RUNNING\nEstimator.tick() : tick()
RUNNING\nEstimator.tick() --> RUNNING\nEvaluatortor.tick()\nExecutor.tick() : tick()&&\nEstimator.status()==SUCCESS
RUNNING\nEstimator.tick() --> FAILURE : Estimator.status()!=SUCCESS
RUNNING\nEvaluatortor.tick()\nExecutor.tick() --> RUNNING\nEvaluatortor.tick()\nExecutor.tick() : tick()&&\nExecutor.status()==RUNNING&&\nEvaluator.status()!=SUCCESS
RUNNING\nEvaluatortor.tick()\nExecutor.tick() --> SUCCESS : Evaluator.status()==SUCCESS
RUNNING\nEvaluatortor.tick()\nExecutor.tick() --> RUNNING\nEvaluator.tick() : tick()&&\nExecutor.status()==SUCCESS
RUNNING\nEvaluatortor.tick()\nExecutor.tick() --> FAILURE : Executor.status()==FAILURE
RUNNING\nEvaluator.tick() --> SUCCESS : Evaluator.status()==SUCCESS
RUNNING\nEvaluator.tick() -->[*] : onHalted()
RUNNING\nEvaluator.tick() --> FAILURE : Evaluator.status()==FAILURE
RUNNING\nEvaluatortor.tick()\nExecutor.tick() --> [*] : onHalted()
RUNNING\nEstimator.tick() --> [*] : onHalted()
SUCCESS --> [*]
FAILURE --> [*]
Three Action nodes are provided, i.e. the Executor, Evaluator, and the Estimator. The Executor is a ROS SimpleActionClient
of the action type HighLevelRequest
. The Evaluator utilizes the feedback of the ROS action. The Estimator is a SimpleServiceClient
of the service type ModuleEstimate
.
BehaviorTree.ROS
BehaviorTree.CPP is a middleware independent framework to develop Behavior Trees in C++.
The library is not particularly opinionated about the way Actions and Conditions should be created; this gives more freedom to the developer, but can also be confusing for those people which are getting started with it.
Consequently, many people in the ROS community asked for examples and guidelines; this repository try to provide some basic examples.
Currently, two wrappers are provided:
-
RosServiceNode, which can be used to call ROS Services
-
RosActionNode that, similarly, is a wrapper around actionlib::SimpleActionClient.