事件驱动框架(二)——状态机

2023-05-16

事件驱动框架(二)

说明

本篇接上一篇事件驱动框架之后,介绍状态机的原理相关的,以及事件驱动框架下事件处理状态机的实现。因为代码大多还是参照QP源码,所以仅供学习使用。


有限状态机介绍

有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

引用百度百科:http://baike.baidu.com/link?url=H4dYy2lfIm5yiGVQ0ExlYjEKCHFwByR1iBaySgXcQke9K7C4xPF05Ji8mYyvTzrTlzKBg9UJDXLhtbe_DGdtbWlcMUK-u3XdTpzlNODiLkR8vfU2YfqJxBy1L0YnurXcjBzG2HXC0R1Dkks3wkQz2a

状态机的概念在很多地方都有用到。我在学数字电路的时候和编程的时候都有接触过这种数学模型:其实说白了就是一种数学模型,它的原理都是相似的。这里主要在嵌入式编程中的引用。
网上应该有状态机最简单的点小灯的例子来借助理解状态机怎么编程,这里就直接放传送门了。

传送门空缺中。。。。

当然还有种层次状态机(HSM),这种直接把有限状态机(FSM)给包括进去了。结构上具有层次性,而且设计和实现都比较复杂。可以记住UML图来进行设计。


事件处理状态机的实现

该部分参考了一部分的QP框架。QP的设计主要实现层次为实现层次状态机(HSM)的方式:规定每次事件处理完后的状态返回值(这个主要是根据UML图的模型,也就是必须开发者之前设计过的来实现的),状态转换过程中会自动根据返回值,判断实现是否需要调用进入和退出的状态回调。但是该种方式有一个缺点:它需要开发者对QP非常了解这个框架,函数回调是采用列举事件(switch)的方式,并且需要自己逻辑上使用状态机的编程思想,并且需要严格对照着UML图的状态转换方式进行编程。

一.状态机的常见处理方式:

下再介绍几种常用的状态机处理方式:但这些都不外乎提供2个接口:dispatch和initial。initial为初始化函数,即负责这个状态机控制的对象的初始化,及其设置状态机的初始状态。dispatch接口用来给状态机派发一个事件。

1>.在dispatch中列出所有的状态及对应的事件。

其实QP在这个功能上有点隐藏状态的意思。缺点就是需要把所有状态和时间都罗列,代码的重用性较低,内容容易膨胀。

2>.状态表(State Table)

该种方式是将事件和状态绑定到一张二维表上,然后根据不同的状态和时间进行规律的映射到这张表上,进行事件处理的回调。
缺点:这是一张静态的状态表,需要列举出所有的事件和状态,但状态和事件的类型增多时,维护就变得比较困难。

3>.面向对象的设计模式

这种方式是把状态机作为一个对象,将状态的原型作为一个抽象类,然后子状态(具体状态)继承于抽象类,完成它的具体实现。个人感觉这种方式和上一种差不多,不过上一种相对来说多出了一张状态表。
缺点:多态的实现用C语言比较麻烦。如果继承每个状态还是需要列举出所有状态的实现的,如果对应的状态的事件为空,还是需要建立某个响应,比较浪费内存。如果是继承dispatch,则也需要在单个函数体内列举出事件响应。

4>.pt协程

PT协程会在后面专门章中介绍。

二.FSM的策略

由于这个框架主要还是为了来做GUI的。整个框架采用事件驱动的结构来实现,主要形式采用状态表的形式。
这里讨论一点:就是HSM和FSM的关系。HSM是一种覆盖面很广的模型,包括了各种状态机的模型。但他的层次特征主要体现在对相同的状态类进行抽象,把同样的动作进行归并,比如说一个层次包含了它内包含多个具体状态的进入和退出的相同的操作。这样就减少了代码量和逻辑上的重复性。但如果不用抽象的概念,HSM依然是可以展开成拓展型FSM的(对于这种状态机有个专业的名字,忘了)。而QP的主要的状态机还是针对于HSM模型的,但HSM不容易理解,这也可能是限制了QP推广的原因。
因此在形式上,之前使用过的MATLAB的GUIDE和VB甚至是C#的界面设计时,他的动作响应callback回调函数令我印象深刻。比如:我希望鼠标按下图形化按钮后进行一个动作。这里我只需要在那个动作响应的函数里编程就可以了,剩下的事整个框架则会帮我自动完成,我无需关心它是如何被调用的。利用这个想法,因此我更偏向于使用状态表的方式。但是为了兼容一些类型的状态机,我仿照了QP的一些做法,并可选择性的保留了entry和exit的回调。当然,如果当前对象如果对此并没有要求,则可以省去。
除了SGUI以外,对于其他的对象来说,也可以采取这种策略或者其他几种常见的处理方式。

三.具体实现

1.事件的实现

事件作为驱动整个框架的关键,对象之间通过接受事件,进行响应处理,状态的转化。事件驱动的结构如下:

typedef  struct  AEvtTag  AEvt;
struct AEvtTag
{
        ASignal sig;         /* 事件信号量 */
        uint16_t poolID;      /* 内存池编号,对应的是事件块编号 */
};

ASignal 为型号量的类型,信号的多少可以在头文件中定义,选择不超出范围的大小。poolID是对应的事件编号。动态事件是从内存池中分配出来的,可以动态生成和释放,而静态事件则不能。

2.状态机的实现(SGUI的状态机)

状态机的机构如下:

struct AFsmTag
{
        const AStateHandler * state_table;  /* 指向状态表 */
        ASignal  n_signals;                 /* 信号量总数 */
        uint8_t  n_states;                  /* 状态总数 */
        uint8_t  state;                     /* 当前状态编号 */
        uint8_t  method ;                   /* 方式: 提供2种:是否省略进入退出动作,默认忽略。
                                             这里设置这个主要是为了省内存,如果找到更好的映射方式时改进*/
        AStateHandler initial;              /* 初始化 转换 */

};

其中大部分都是状态表设计的原型,用来辅助查表的实现的。这里我增加了一个method的变量,用来标志着个是否是基础的FSM带进入entry和exit的事件的。如果对于状态较多的对象,如果不需要用到进入和退出的动作不必要,则可以省去一大笔空间。
dipatch函数主要是实现事件的派送交给响应函数处理,另外如果有entry和exit,则会自动调用。不过这里有个比较麻烦的问题:用户在每次处理函数过后还是需要返回一个状态值(改变状态或者不改变)。

3.初始化

状态机初始化代码如下:显示调用初始化接口

/*!     预留信号状态     */
enum 
{
    A_ENTRY_SIG = 0,                     /* 进入状态动作 */
    A_EXIT_SIG,                          /*退出状态动作 */
    A_DEFAULT_SIG                        /*用户预留信号 */
};

void AFsm_init(AFsm *me)
{
        AStateHandler t;
        (me->initial)(me, (AEvt *)0);  //调用对象初始化函数
        assert((me->state) < (me->n_state));
        if ((me->method == FSM_DEFAULT_METHOD))       
        {
            t = *(me->state_table + me->state * me->n_signals + A_ENTRY_SIG);  //默认进入预留 
            (*t)(me, (void *)0);
        }
        else
        {
            t = *(me->state_table + me->state * me->n_signals);          //默认进入第一个状态
            (*t)(me, (void *)0);
        }
}

前面的枚举量为预留进入退出的信号标识。如果设置为FSM_DEFAULT_METHOD模式,则需要在该对象的第一个信号枚举=A_DEFAULT_SIG。那SGUI的自定义的一个响应举例:

/*! 自定义按键 */
enum KeyCode
{
        TICK_SIG = A_DEFAULT_SIG,           
        UP_KEY_SIG,
        DOWN_KEY_SIG,
        LEFT_KEY_SIG,
        RIGHT_KEY_SIG,
        CONFIRM_KEY_SIG,
        GUI_MAX_SIG
};

这样就默认将进入和退出的信号无形中加入到自定义的信号钱

初始化接口会自动调用对象的初始化函数,再将状态转换到默认状态:如果有设置状态的退出进入则会默认进入entry状态进行执行,否则进入第一个状态的第一个响应进行执行。(这里想着是否能改进一下,制定一下第一次进入的状态和时间)。

4.调度

状态机调度代码如下:

void AFsm_dispatch(AFsm * me, AEvt const * e)
{
    AStateHandler t;
    uint8_t sta;
    assert(e);//事件合法性
    assert(state);//状态合法性
    sta = me->state;
    t = *(me->state_table + me->state * me->n_signals + e->sig);  //获取状态表中的函数指针

    if ((*t)(me, e) == A_RET_TRANS && (me->method == FSM_DEFAULT_METHOD))  //得到执行结果
    {
        /* 调用退出 */
        t = *(me->state_table + sta * me->n_signals + A_EXIT_SIG);
        (*t)(me, (void *)0);

        /* 调用进入 */
        t = *(me->state_table + me->state * me->n_signals + A_ENTRY_SIG);       
        (*t)(me, (void *)0);
    }
}

如果该对象没有设置进入或者退出状态,则不执行进入退出段响应的代码。如果有设置FSM_DEFAULT_METHOD模式则在状态发生改变时自动调用进出状态的函数。

4.状态空响应

状态空响应的代码用来填充到状态表中对应状态和事件什么都不做的位置。这部分代码如下:

void AFsm_empty(AFsm * me, AEvt const * const e)
{
        (void)me;
        (void)e;
}

四.小结

事件处理的状态机大概就这样了,原型还是用了状态表的原理。后来有听别人说过PT协程的方法来封装状态机,大概记得的印象中主要就是把while-case的结构隐藏到范式中,但实际上还是状态机的结构,但整体上看起来就和顺序编程没什么差别。因为看过很久了,而且正好要思考怎么拓展框架的灵活性,所以会单独拉一张来介绍下PT协程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

事件驱动框架(二)——状态机 的相关文章

  • subprocess 使用执行 cmd

    参考 Python模块之subprocess用法实例详解 腾讯云开发者社区 腾讯云 上面这个链接主要说了一些用法 补充 通用示例 先来看一个的用法 xff0c 执行 ls 命令 xff0c 返回结果 xff0c 没有报错 xff0c 执行完
  • 多旋翼无人机小知识

    https blog csdn net np4rHI455vg29y2 article details 78954871 根据牛顿第三定律 xff0c 旋翼在旋转的同时 xff0c 也会同时向电机施加一个反作用力 xff08 反扭矩 xff
  • RTK

    实时动态技术 xff08 英语 xff1a Real Time Kinematic xff0c RTK xff09 是实时动态载波相位差分技术的简称 xff0c 是一种通过基准站和流动站的同步观测 xff0c 利用载波相位观测值实现快速高精
  • github Tags和Branch分支相关操作(三)

    Tag推送 1 git查看tag命令 xff1a git tag 2 创建Tag直接加入名字就好了 xff0c 格式 xff1a git tag 名字 m 注释 这时只能在本地可以看到自己新建Tag xff0c 在远程中央仓库中还是看不到在
  • PostMan中文汉化亲测好用!!!!

    PostMan 中文汉化版 最新版本 xff1a 8 10 0 更新时间 xff1a 2021 08 07 请认真阅读存储库的自述文件README md 汉化包下载链接 https github com hlmd Postman cn re
  • ubuntu下安装多个版本的python

    本身系统安装了python2 7和python3 5 现在由于开发需要 xff0c 还需要安装python3 6 安装步骤如下 xff1a 1 下载 Python 直接在官网下载 Python的源代码 xff0c 解压缩 xff0c 按照
  • stm32标准库文件内容说明

    目录 思维导图 一 stm32f4xx h文件内容说明 1 定义了HSE时钟频率的值 2 定义了启动HSE时钟的超时时间 xff0c HSI时钟频率的值 3 根据不同的芯片定义中断向量表结构体 4 包含一些头文件 5 对一些数据类型进行重命
  • UART协议详解

    UART使用的是 异步 xff0c 串行通信 串行通信是指利用一条传输线将资料一位位地顺序传送 特点是通信线路简单 xff0c 利用简单的线缆就可实现通信 xff0c 降低成本 xff0c 适用于远距离通信 xff0c 但传输速度慢的应用场
  • C++中的.和::和:和-」的区别

    转自 xff1a https blog csdn net liao392781 article details 97931845 在学习C 43 43 的过程中我们经常会用到 和 和 xff1a 和 gt xff0c 在此整理一下这些常用符
  • hpsocket实现HTTP通信功能

    前段时间朋友急需让我帮忙搞定个C 43 43 的http库 xff1f xff1f xff1f xff1f 然后指定了hpsocket这个库 看了一下是国人写了 xff0c 资料在doc还算比较全 xff0c 整理了很多框架的部分也挺详细
  • Mysql 批量修改四种方式效率对比(一)

    文章目录 Mysql 批量修改四种方式效率对比环境信息测试数据pom 依赖数据库初始化测试数据 批量修改方案第一种 for第二种 case when第三种 replace into第四种 ON DUPLICATE KEY UPDATE 测试
  • idea插件-SonarLint(六)

    文章目录 功能插件名称效果 功能 检查代码bug xff0c 排除编码集错误 插件名称 SonarLint 效果 以下只是演示可以进行bug提示及优化 xff0c 还有更多 修复意见 xff1a 使用 logger 替代 System ou
  • idea插件安装方式(七)

    插件安装两种方式 在线安装 通过 Preferences gt Plugins gt marketplace 在线安装 离线安装 通过 https plugins jetbrains com 离线安装 比如 Lombok xff0c 找对应
  • mysql-递归查询(二)

    文章目录 情景描述数据准备递归sql 情景描述 mysql 表查询 xff0c 经常有通过子不断查询父 xff0c 直至查询不到为止 数据准备 SET NAMES utf8 span class token punctuation span
  • zabbix重启命令

    1 zabbix server服务重启 service zabbix server restart 或者 service zabbix server restart 2 zabbix agentd客户端重启 service zabbix a
  • 使用MediaPlayer实现播放、暂停、继续和停止功能的简易音乐播放器

    1 布局文件 lt xml version 61 34 1 0 34 encoding 61 34 utf 8 34 gt lt LinearLayout xmlns android 61 34 http schemas android c
  • H3C交换机web管理配置

    1 下载一个超级终端到PC机上 由于工作环境不能联网的环境下 xff0c 下载了好几个超级终端才能用 2 超级终端连接有很多教程 但是有一个重要的事情 xff1a 在COM1属性的端口设置里面 xff0c 将 xff08 位 B xff09
  • 交换机MIB浏览器ifType、ifDescr、ifMtu、ifInOctets等的含义

    1 ifType 接口的类型 取值117表示接口为GigabitEthernet 取值62表示借口为FastEnthernet 2 ifDescr 接口类型的描述 有GigabitEthernet FastEthernet等 3 ifMtu
  • mac下PyCharm导入第三方包

    最近对python有兴趣 xff0c 在网上搜了 八天深入理解python 视频教程 xff0c 由于用的mac xff0c 不用安装python xff0c 感觉瞬间开发环境就已经具备了 xff0c 然后连个ipython都没装上 xff
  • Failed to execute goal org.mybatis.generator:mybatis-generator-maven-plugin:1.3.2:generate (default-

    Failed to execute goal org mybatis generator mybatis generator maven plugin 1 3 2 generate default cli on project ssm Ex

随机推荐

  • windows系统下QT打包后到不同系统的各种报错问题分析

    最近碰到好多人问关于WINDOWS上的QT打包后在自己电脑上可以运行 xff0c 换台电脑报各种错误的问题 于是特地写个帖子总结下一些问题 xff1a 首先是打包QT xff0c 将QT的系统库拉出来 这里可以借用windeployqt工具
  • python使用requests提交post请求并上传文件(multipart/form-data)

    目录 一 背景 二 请求接口上传文件 2 1 分析接口 2 2 python进行请求 三 总结 一 背景 也是前几天 xff0c 有一个需求上传文件需要自动化 具体是上传到系统一个文件 xff0c 并收到返回结果 考虑使用python的re
  • Optitrack光学动作捕捉

    光学式动作捕捉依靠一整套精密而复杂的光学摄像头来实现 xff0c 它通过计算机视觉原理 xff0c 由多个高速摄像机从不同角度对目标特征点进行跟踪来完成全身的动作的捕捉 美国大片中的后期特效制作大都是用的这种光学式动捕技术打开百度APP x
  • 带参数的宏的问题

    include 34 iostream 34 using namespace std define COMPUTE XX a a a 43 a 2 int main int a 61 2 int test1 61 COMPUTE XX 43
  • Qt TCP/UDP通讯封装

    Qt TCP UDP通讯封装 TCP客户端 class TcpClient public QTcpSocket Q OBJECT public explicit TcpClient QObject parent 61 nullptr Tcp
  • http post请求

    1 引入所需要的jar包 lt dependency gt lt groupId gt org apache httpcomponents lt groupId gt lt artifactId gt httpclient lt artif
  • C语言截取从某位置开始指定长度子字符串方法

    C语言中没有从某位置开始指定长度子字符串的函数 xff08 还是我没找到 xff1f xff09 xff0c 只有从头开始截取的strncat和strncpy xff0c 以strncpy为例 xff1a char dest 4 61 34
  • PX4中自定义MAVLink消息(记录)

    简单记录一下这个过程 一 自定义uORB消息 这一步比较简单 xff0c 首先在msg 中新建ca trajectory msg文件 uint64 timestamp time since system start span class t
  • HP-SOCKET学习笔记(一)

    介绍 HP Socket是一套通用的高性能TCP UDP HTTP 通信框架 xff0c 包含服务端组件 客户端组件和Agent组件 xff0c 广泛适用于各种不同应用场景的TCP UDP HTTP通信系统 xff0c 提供C C 43 4
  • curl断点续载

    curl断点续传 xff0c 下载过程中关闭控制台 xff0c 然后重新启动 xff0c 又会接着下载 include 34 stdafx h 34 include lt io h gt include 34 curl curl h 34
  • curl多线程下载实现

    其实libcurl自带一个应用 xff0c 很高大上 xff0c 但是作为范例参考怎么使用libcurl觉得不大适合 xff01 还是写一些helloworl的程序的 xff0c 一目了然 include 34 stdafx h 34 in
  • arduino实现tts

    淘宝上查了一下 xff0c 目前的几款tts模块貌似指令格式类似 Blink Turns on an LED on for one second then off for one second repeatedly This example
  • openwrt编译error: ext4_allocate_best_fit_partial: failed to allocate 13 blocks, out of space?

    编译openwrt时候报如下错误 xff1a Creating filesystem with parameters Size 50331648 Block size 4096 Blocks per group 32768 Inodes p
  • 在mac上创建鼠标双击可执行的shell脚本

    总是觉得mac权限管理好严格 xff0c 要创建向在window上执行bat那样的脚本需要如下操作 首先创建测试脚本 touch clickexe sh open e clickexe sh 在脚本中输入内容 echo 34 hello w
  • rm: cannot remove Permission denied 问题解决方法

    今天编译openwrt系统的时候 xff0c 碰到这样的问题 rm cannot remove xxx Permission denied 但是又不允许用root用户执行 xff0c 所以就要用root用户去修改权限 chmod 777 如
  • Arduino 舵机驱动板编程

    需要下载Adafruit的arduino库 xff0c 这个网上搜索一下很多 我的驱动板是16路基于I2C接口通信 xff0c 这个arduino库底层都做好了 xff0c 精度是12位 xff08 4096 xff09 设置非常简单 xf
  • 3d图形引擎结构

    其实3d引擎结构基本上都是类似的 xff0c 差别也只是细节的上的差别 xff0c 如jme引擎的结构如下 xff1a 首先是viewport xff0c 这个就像2d图层一样 xff0c 每个viewport开始渲染的时候都可以清除缓冲区
  • 树莓派开机启动frpc

    直接在 rc local里启动frpc失败 xff0c 原因是网络好像连接失败 所以写了个shell脚本 xff0c 通过sleep延时一下 xff0c 就启动成功了 首先建立startfrp sh bin bash cd home pi
  • esp32 arduino psram使用

    esp32 arduino固件是已经支持psram了的 xff0c 是模式2 xff0c 所以要使用heap caps malloc来分配 注意选择wrover modelus xff0c 其他的可能驱动不支持 示例代码 xff1a inc
  • 事件驱动框架(二)——状态机

    事件驱动框架 xff08 二 xff09 说明 本篇接上一篇事件驱动框架之后 xff0c 介绍状态机的原理相关的 xff0c 以及事件驱动框架下事件处理状态机的实现 因为代码大多还是参照QP源码 xff0c 所以仅供学习使用 有限状态机介绍