分享一款状态机自动生成工具

为实用的软件系统编写状态机并不是一件十分轻松的事情,特别是当状态机本身比较复杂的时候尤其如此,许多有过类似经历的程序员往往将其形容为毫无创意的过程,因为他们需要将大量的时间与精力倾注在如何管理好状态机中的各种状态上,而不是程序本身的运行逻辑。
作为一种通用的软件设计模式,各种软件系统的状态机之间肯定会或多或少地存在着一些共性,因此人们开始尝试开发一些工具来自动生成有限状态机的框架代码,而在linux下就有一个挺不错的选择──fsme(finite state machine editor)。
可视化的fsme
fsme是一个基于qt的有限状态机工具,它能够让用户通过图形化的方式来对程序中所需要的状态机进行建模,并且还能够自动生成用c++或者python实现的状态机框架代码。
下面就以下图中城门的状态机为例,来介绍如何利用fsme来自动生成程序中所需要的状态机代码。
控制城门的状态机
1状态机建模 首先运行fsme命令来启动状态机编辑器,然后单击工具栏上的new按钮来创建一个新的状态机。fsme中用于构建状态机的基本元素一共有五种:事件(event)、输入(input)、输出(output)、状态(state)和转换(transition),在界面左边的树形列表中可以找到其中的四种。
状态建模 在fsme界面左边的树形列表中选择states项,然后按下键盘上的insert键来插入一个新的状态,接着在右下方的name文本框中输入状态的名称,再在右上方的绘图区域单击该状态所要放置的位置,一个新的状态就创建好了。用同样的办法可以添加状态机所需要的所有状态,如下图所示。
状态建模
事件建模 在fsme界面左边的树形列表中选择events项,然后按下键盘上的insert键来添加一个新的事件,接着在右下方的name文本框中输入事件的名称,再单击apply按钮,一个新的事件就创建好了。用同样的办法可以添加状态机所需要的所有事件,如下图所示。
转换建模 状态转换是整个建模过程中最重要的一个部分,它用来定义有限状态机中的一个状态是如何切换到另一个状态的。例如,当用来控制城门的状态机处于opened状态时,如果此时有close事件产生,那么状态机的当前状态将切换到closed状态,这样一个完整的过程在状态机模型中可以用closedoor这样一个转换来进行描述。
要在fsme中添加这样一个转换,首先需要在界面左边的树形列表中选择states下的opened项,然后按下键盘上的insert键来添加一个新的转换,接着在右下角的name文本框中输入转换的名字closedoor,在condition文本框中输入close表明触发该转换的条件是事件close的产生,在target下拉框中选择closed项表明该转换发生后状态机将被切换到closed状态,最后再单击apply按钮,一个新的状态转换关系就定义好了,如下图所示。用同样的办法可以添加状态机所需要的所有转换。
转换建模
2 生成状态机框架 使用fsme不仅能够进行可视化的状态机建模,更重要的是它还可以根据得到的模型自动生成用c++或者python实现的状态机框架。首先在fsme界面左边的树形列表中选择root项,然后在右下角的name文本框中输入状态机的名字doorfsm,再从initial state下拉列表中选择状态opened作为状态机的初始化状态,如图6所示。
设置初始属性
在将状态机模型保存为door.fsm文件之后,使用下面的命令可以生成包含有状态机定义的头文件:
[xiaowp@linuxgam code]$ fsmc door.fsm -d -o doorfsm.h
进一步还可以生成包含有状态机实现的框架代码: [xiaowp@linuxgam code]$ fsmc door.fsm -d -impl doorfsm.h -o doorfsm.cpp
如果想对生成的状态机进行验证,只需要再手工编写一段用于测试的代码就可以了:
/* * testfsm.cpp * 测试生成的状态机框架 */ #include doorfsm.h int main() { doorfsm door; door.a(doorfsm::close); door.a(doorfsm::lock); door.a(doorfsm::unlock); door.a(doorfsm::open); }
有限状态机是由事件来进行驱动的,在fsme生成的状态机框架代码中,方法a()可以被用来向状态机发送相应的事件,从而提供状态机正常运转所需要的动力。状态机负责在其内部维护一个事件队列,所有到达的事件都会先被放到事件队列中进行等候,从而能够保证它们将按照到达的先后顺序被依次处理。在处理每一个到达的事件时,状态机都会根据自己当前所处的状态,检查与该状态对应的转换条件是否已经被满足,如果满足的话则激活相应的状态转换过程。
使用下面的命令能够将生成的状态机框架和测试代码编译成一个可执行文件:
[xiaowp@linuxgam code]$ g++ doorfsm.cpp testfsm.cpp -o fsm
由于之前在用fsmc命令生成状态机代码时使用了-d选项,生成的状态机框架中会包含一定的调试信息,包括状态机中每次状态转换时的激活事件、转换前的状态、所经历的转换、转换后的状态等,如下所示:
  [xiaowp@linuxgam code]$ ./fsm doorfsm'close' doorfsm'opened' doorfsm'closedoor' doorfsm:new state:'closed' doorfsm'lock' doorfsm'closed' doorfsm'lockdoor' doorfsm:new state:'locked' doorfsm'unlock' doorfsm'locked' doorfsm'unlockdoor' doorfsm:new state:'unlocked' doorfsm'open' doorfsm'unlocked' doorfsm'opendoor' doorfsm:new state:'opened'
3定制状态机
目前得到的状态机已经能够响应来自外部的各种事件,并适当地调整自己当前所处的状态,也就是说已经实现了状态机引擎的功能,接下来要做的就是根据应用的具体需求来进行定制,为状态机加入与软件系统本身相关的那些处理逻辑。在fsme中,与具体应用相关的操作称为输出(output),它们实际上就是一些需要用户给出具体实现的虚函数,自动生成的状态机引擎负责在进入或者退出某个状态时调用它们。
仍然以控制城门的那个状态机为例,假设我们希望在进入每个状态时都添加一部分处理逻辑。首在fsme界面左边的树形列表选择outputs项,然后按下键盘上的insert键来添加一个新的输出,接着在右下方的name文本框中输入相应的名称,再单击apply按钮,一个新的输出就创建好了,如图所示。用同样的办法可以添加状态机所需要的所有输出。
添加输出
当所有的输出都定义好之后,接下来就可以为状态机中的每个状态绑定相应的输出。首先在fsme界面左侧的states项中选择相应的状态,然后从右下角的available列表框中选择与该状态对应的输出,再单击<按钮将其添加到in列表中,如图所示。用同样的办法可以为状态机中的所有状态设置相应的输出,同一个状态可以对应有多个输出,其中in列表中的输出会在进入该状态时被调用,而out列表中的输出则会在退出该状态时被调用,输出调用的顺序是与其在in或者out列表中的顺序相一致的。
图为状态设置输出
由于对状态机模型进行了修改,我们需要再次生成状态机的框架代码,不过这次不需要加上-d参数:
[xiaowp@linuxgam code]$ fsmc door.fsm -o doorfsm.h [xiaowp@linuxgam code]$ fsmc door.fsm -d -impl doorfsm.h -o doorfsm.cpp
我们在新的状态机模型中添加了enteropend、enterclosed、enterlocked和enterunlocked四个输出,因此生成的类doorfsm中会包含如下几个纯虚函数:
  virtual void enteropened() = 0; virtual void enterlocked() = 0; virtual void enterunlocked() = 0; virtual void enterclosed() = 0;
显然,此时生成的状态机框架不能够再被直接编译了,我们必须从类doorfsm派生出一个子类,并提供对这几个纯虚函数的具体实现:
  /* * doorfsmlogic.h * 状态机控制逻辑的头文件 */ #include doorfsm.h class doorfsmlogic : public doorfsm { protected: virtual void enteropened(); virtual void enterlocked(); virtual void enterunlocked(); virtual void enterclosed(); };
正如前面所提到过的,这几个函数实际上代表的正是应用系统的处理逻辑,作为例子我们只是简单地输出一些提示信息:
/* * doorfsmlogic.cpp * 状态机控制逻辑的实现文件 */ #include doorfsmlogic.h #include void doorfsmlogic::enteropened() { std::cout << enter opened state. << std::endl; } void doorfsmlogic::enterclosed() { std::cout << enter closed state. << std::endl; } void doorfsmlogic::enterlocked() { std::cout << enter locked state. << std::endl; } void doorfsmlogic::enterunlocked() { std::cout << enter unlocked state. << std::endl; }
同样,为了对生成的状态机进行验证,我们还需要手工编写一段测试代码:
  /* * testfsm.cpp * 测试状态机逻辑 */ #include doorfsmlogic.h int main() { doorfsmlogic door; door.a(doorfsm::close); door.a(doorfsm::lock); door.a(doorfsm::unlock); door.a(doorfsm::open); }
使用下面的命令能够将生成的状态机框架和测试代码编译成一个可执行文件:
  [xiaowp@linuxgam code]$ g++ doorfsm.cpp doorfsmlogic.cpp testlogic.cpp -o logic
运行结果如下所示:
  [xiaowp@linuxgam code]$ ./logic enter closed state. enter locked state. enter unlocked state. enter opened state.  


比较物联网云平台时应注意什么
深度探析中国超级智能城市指数
全球主要通信运营商均青睐泰克通信公司的解决方案
可支持超过30倍智能变焦的4K会议摄像机工程的应用分析
谷歌将强制要求PWA应用提供离线体验
分享一款状态机自动生成工具
WitsView指出,iPhone X销售状况不如预期 OLED面板风潮却已经成形
ASIC和FPGA区别及建模概念
焊接贴片电阻的关键步骤
宽范围高稳晶振频率稳定度测试系统的设计
嵌入式大趴回归!2023 STM32全国巡回研讨会开放报名
蔚来汽车百亿布局武汉 首款SUV将于2017年首发
自制遥控车DIY图解
CadenceLIVE China 2023丨验证专题 1 议程揭晓
解密2023年三大汽车传感器趋势!
如何打造一款能轻松上手的PCIe测试工具?
铜线焊接机的挑选需要考虑什么
爆iPhone 12手机内置36块磁铁,只为无线充电功能
智能座舱域控制器技术发展趋势分析
小米新子品牌手机曝光,定价1999,自诩最强人像摄影手机,矛头直指O、V旗舰