提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
TODO:写完再整理
本文先对简单的有限状态机设计方法做个简单的介绍,具体内容后续再更,其他模块可以参考去我其他文章
提示:以下是本篇文章正文内容
本文举例是以fast_planning开源工程为例子抽象出来的
FSM可进一步区分为确定型(Deterministic)和非确定型(Non-Deterministic)自动机。在确定型自动机中,每个状态对每个可能输入只有精确的一个转移。在非确定型自动机中,给定状态对给定可能输入可以没有或有多于一个转移。
画状态转移图出来可以理清楚所有的状态和转移条件,排查合理性,例如不能存在状态进去了就出不来导致系统卡死【防盗标记–盒子君hzj】
画状态转移图最重要的是更清楚的知道状态机有没有和业务匹配,排查合理性【防盗标记–盒子君hzj】
这个变量状态的一般定义在.h对应的class里面,状态机实现的逻辑部分写在.cpp中
【防盗标记–盒子君hzj】
class KinoReplanFSM {
private:
/* ---------- FSM flag ---------- */
enum FSM_EXEC_STATE { INIT, WAIT_TARGET, GEN_NEW_TRAJ, REPLAN_TRAJ, EXEC_TRAJ, REPLAN_NEW };
FSM_EXEC_STATE exec_state_;
/*--- fsm functions ---*/
void execFSMCallback(const ros::TimerEvent& e);
void changeFSMExecState(FSM_EXEC_STATE new_state, string pos_call);
void printFSMExecState();
/*--- other functions ---*/
//状态机某个状态中的实现逻辑函数
/* ---other flag --- */
//其他模块的标志位
/* ---other parameters--- */
//其他模块的参数
/* ---other data--- */
//其他模块的数据
/* ---other modular API--- */
//其他模块的API或者代理
public:
KinoReplanFSM(/* args */) { //FSM构造函数
}
~KinoReplanFSM() { //FSM析构函数
}
void init(ros::NodeHandle& nh);//FSM初始化函数
}
.
.
这一函数一般是先设置了一系列参数,实例化一系列模块算法的类单例,初始化一系列系统组件,等等。具体步骤就在这些组件的调用及回调函数/定时器函数里实现。【防盗标记–盒子君hzj】
void KinoReplanFSM::init(ros::NodeHandle& nh) {
/* 系统其他模块状态的一些flag状态 */
/* initialize main modules */
/* fsm param */
/* 话题、服务、定时器的callback */
}
.
.
状态转移函数的作用是负责在符合条件的情况下,进行有限状态即FSM的状态转移,每次状态转移的时候最好打印输出一下日志【防盗标记–盒子君hzj】
//状态转移函数
void FSM::changeFSMExecState(FSM_EXEC_STATE new_state, string pos_call) {
string state_str[5] = { "INIT", "WAIT_TARGET", "GEN_NEW_TRAJ", "REPLAN_TRAJ", "EXEC_TRAJ" };
int pre_s = int(exec_state_);
exec_state_ = new_state;
cout << "[" + pos_call + "]: from " + state_str[pre_s] + " to " + state_str[int(new_state)] << endl;
}
.
.
void FSM::printFSMExecState() {
string state_str[5] = { "INIT", "WAIT_TARGET", "GEN_NEW_TRAJ", "REPLAN_TRAJ", "EXEC_TRAJ" };
cout << "[FSM]: state: " + state_str[int(exec_state_)] << endl;
}
.
.
注意:这个函数必须是定时循环调用的,最好单独开一个线程给它,不被打断提高稳定性,不过在定时,或者main的死循环里面实现也是可以的【防盗标记–盒子君hzj】
void FSM::execFSMCallback(const ros::TimerEvent& e) {
//(1)设置状态机的运行频率,同时检查系统其他被FSM调用的模块的状态,同时定时打印一次当前执行状态
static int fsm_num = 0;
fsm_num++;
if (fsm_num == 100) {
printFSMExecState();
if (!have_odom_) cout << "no odom." << endl;
if (!trigger_) cout << "wait for goal." << endl;
fsm_num = 0;
}
//(2)根据执行状态变量 exec_state_进入switch循环,进行状态转移
switch (exec_state_) {
case INIT: {
/*(1)运行该状态相关的算法逻辑功能函数*/
//注意算法逻辑功能函数运行时间不能超过FSM的运行周期,
//甚至算法逻辑功能函数内不能有死循环
//不然状态本次状态还没有运行完,就出发下一次运行,系统会有偶然的bug出现
/*(2)判断有限状态机FSM的状态转移条件,若符合则进行状态转移*/
if(XXX1){
changeFSMExecState(XXX1, "FSM");
}
else if(XXX2){
changeFSMExecState(XXX2, "FSM");
}
else{
changeFSMExecState(XXX3, "FSM");
}
/*(3)跳出该状态*/
break;
}
case WAIT_TARGET: {
/*(1)运行该状态相关的算法逻辑功能函数*/
//注意算法逻辑功能函数运行时间不能超过FSM的运行周期,
//甚至算法逻辑功能函数内不能有死循环
//不然状态本次状态还没有运行完,就出发下一次运行,系统会有偶然的bug出现
/*(2)判断有限状态机FSM的状态转移条件,若符合则进行状态转移*/
if(XXX1){
changeFSMExecState(XXX1, "FSM");
}
else if(XXX2){
changeFSMExecState(XXX2, "FSM");
}
else{
changeFSMExecState(XXX3, "FSM");
}
/*(3)跳出该状态*/
break;
}
case GEN_NEW_TRAJ: {
/*(1)运行该状态相关的算法逻辑功能函数*/
//注意算法逻辑功能函数运行时间不能超过FSM的运行周期,
//甚至算法逻辑功能函数内不能有死循环
//不然状态本次状态还没有运行完,就出发下一次运行,系统会有偶然的bug出现
/*(2)判断有限状态机FSM的状态转移条件,若符合则进行状态转移*/
if(XXX1){
changeFSMExecState(XXX1, "FSM");
}
else if(XXX2){
changeFSMExecState(XXX2, "FSM");
}
else{
changeFSMExecState(XXX3, "FSM");
}
/*(3)跳出该状态*/
break;
}
case EXEC_TRAJ: {
/*(1)运行该状态相关的算法逻辑功能函数*/
//注意算法逻辑功能函数运行时间不能超过FSM的运行周期,
//甚至算法逻辑功能函数内不能有死循环
//不然状态本次状态还没有运行完,就出发下一次运行,系统会有偶然的bug出现
/*(2)判断有限状态机FSM的状态转移条件,若符合则进行状态转移*/
if(XXX1){
changeFSMExecState(XXX1, "FSM");
}
else if(XXX2){
changeFSMExecState(XXX2, "FSM");
}
else{
changeFSMExecState(XXX3, "FSM");
}
/*(3)跳出该状态*/
break;
}
case REPLAN_TRAJ: {
/*(1)运行该状态相关的算法逻辑功能函数*/
//注意算法逻辑功能函数运行时间不能超过FSM的运行周期,
//甚至算法逻辑功能函数内不能有死循环
//不然状态本次状态还没有运行完,就出发下一次运行,系统会有偶然的bug出现
/*(2)判断有限状态机FSM的状态转移条件,若符合则进行状态转移*/
if(XXX1){
changeFSMExecState(XXX1, "FSM");
}
else if(XXX2){
changeFSMExecState(XXX2, "FSM");
}
else{
changeFSMExecState(XXX3, "FSM");
}
/*(3)跳出该状态*/
break;
}
}
}
算法逻辑功能函数一般放置在有限状态机FSM的某个状态实现中,一般把算法逻辑功能函数的数据类型设置成布尔bool型,算法逻辑功能函数运行正常则返回true,算法逻辑功能函数运行不正常或者超时则返回false
为什么要把算法逻辑功能函数设置成bool的数据类型呢?
因为~
第一、不是所有的算法运行都能够输出结果的
第二、通过算法逻辑功能函数返回类型能作为状态转移条件的判断
bool success = callKinodynamicReplan();
if (success) {
changeFSMExecState(EXEC_TRAJ, "FSM");
} else {
// have_target_ = false;
// changeFSMExecState(WAIT_TARGET, "FSM");
changeFSMExecState(GEN_NEW_TRAJ, "FSM");
}
break;
这是较为简单的有限状态机的基本实现框架,具体的根据自己的业务设计状态和状态的实现,或者对状态图进行扩充