VAPS XT入门教程15.02:状态机

系列索引:VAPS XT入门教程索引

上一篇:VAPS XT入门教程15.01:nCOM机制

VAPS XT提供了状态机功能,用于实现不同状态间的切换。

state chart

最左侧的带圆点的为入口,有且只有一个。

Normal为常显状态,从入口进来之后就是Normal状态。

Press为按下状态

鼠标左键简单点击的操作为:Normal->Press->Normal。

按下然后松开。

Naormal->Press的触发条件为evPress,即鼠标按下

Press->Normal的触发条件为evRelease,即鼠标松开。

此图就是最简单的点击一次的操作状态图。

原理

一个健壮的状态机可以让你的程序,不论发生何种突发事件都不会突然进入一个不可预知的程序分支。

这正是VAPS XT这种软件需要的功能,飞机上的软件一切都要是可控的,所有的状态都是可预测的,这也是适航认证的重点。

定义

状态机是有限状态自动机(FSM, Finite State Machine)的简称,是现实事物运行规则抽象而成的一个数学模型。

先来解释什么是“状态”(State)。现实事物是有不同状态的,例如一个LED等,就有 亮 和 灭 两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如LED灯的状态就是两个 亮 和 灭。

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。

举例

以物理课学的灯泡图为例,就是一个最基本的小型状态机
led

可以画出以下的状态机图
state

这里就是两个状态:①灯泡亮,②灯泡灭

如果打开开关,那么状态就会切换为 灯泡亮 。灯泡亮 状态下如果关闭开关,状态就会切换为 灯泡灭。

状态机的全称是有限状态自动机,自动两个字也是包含重要含义的。给定一个状态机,同时给定它的当前状态以及输入,那么输出状态时可以明确的运算出来的。

例如对于灯泡,给定初始状态灯泡灭,给定输入“打开开关”,那么下一个状态时可以运算出来的。

四大概念

下面来给出状态机的四大概念。

  • State ,状态。一个状态机至少要包含两个状态。例如上面灯泡的例子,有 灯泡亮和 灯泡灭两个状态。
  • Event ,事件。事件就是执行某个操作的触发条件或者口令。对于灯泡,“打开开关”就是一个事件。
  • Action ,动作。事件发生以后要执行动作。例如事件是“打开开关”,动作是“开灯”。编程的时候,一个 Action 一般就对应一个函数。
  • Transition ,变换。也就是从一个状态变化为另一个状态。例如“开灯过程”就是一个变换。

下图是VAPS XT transition的图

transition

Trigger就是Event,Action就是对应着Action,Guard应该对应着State,这个过程就是Transition。

通过拖拽实现的状态图也是类似。

Data Flow

Internal Transition和状态图好理解,因为它基本上快把 “我是用状态机实现的” 写在脸上了。

之前我一直不理解Data Flow是怎么实现的,昨晚(2022.06.29)洗澡前突然写到状态机这个功能,瞬间顿悟了。

实现

Data Flow中使用的是某个数据结构的实例

  • 默认情况下,实例创建后会有一个默认值
  • 数值(不管是谁以什么方式)修改后,实例值改变,同时触发数据更新事件
  • 数据更新事件发送后,实例处于等待状态
  • 数据更新事件发送后,进入数据更新回调函数后进入下一个实例的状态机中

Data Flow表格中的应该就是数据更新事件和数据更新回调这个流

根据状态迁移,定义状态机状态如下:

1
2
3
4
5
6
typedef enum{
sta_origin=0,
sta_update,
sta_updated,
sta_wait
}State;

发生的事件如下:

1
2
3
4
5
typedef enum{
evt_wait=0,
evt_update,
evt_updated
}EventId;

不论是状态还是事件都可以根据实际情况增加调整。

定义一个结构体用来表示当前状态转换信息:

1
2
3
4
5
6
typedef struct{
State curState;//当前状态
EventID eventId;//事件ID
State nextState;//下个状态
CallBack action;//回调函数,事件发生后,调用对应的回调函数
}StateTransform;

事件回调函数:实际应用中不同的事件发生需要执行不同的action,就需要定义不同的函数, 为方便起见,本例所有的事件都统一使用同一个回调函数。功能:打印事件发生后进程的前后状态,如果状态发生了变化,就调用对应的回调函数。

1
2
3
4
5
6
7
8
9
10
11
void action_callback(void *arg){
StateTransform *statTran = (StateTransform *)arg;

if(statename[statTran->curState] == statename[statTran->nextState]){
printf("invalid event,state not change\n");
}else{
printf("call back state from %s --> %s\n",
statename[statTran->curState],
statename[statTran->nextState]);
}
}

为各个状态定义迁移表数组:

1
2
3
4
5
6
/*origin*/
StateTransform stateTran_0[]={
{sta_origin,evt_wait, sta_update,NULL},
{sta_origin,evt_update, sta_updated,action_callback},
{sta_origin,evt_updated, sta_wait,NULL},
};

实现event发生函数:

1
void event_happen(unsigned int event)

功能:根据发生的event以及当前的进程state,找到对应的StateTransform 结构体,并调用do_action()

1
void do_action(StateTransform *statTran)

功能:根据结构体变量StateTransform,实现状态迁移,并调用对应的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#define STATETRANS(n)  (stateTran_##n)
/*change state & call callback()*/
void do_action(StateTransform *statTran)
{
if(NULL == statTran)
{
perror("statTran is NULL\n");
return;
}
//状态迁移
globalState = statTran->nextState;
if(statTran->action != NULL)
{//调用回调函数
statTran->action((void*)statTran);
}else{
printf("invalid event,state not change\n");
}
}

void event_happen(unsigned int event)
{
switch(globalState)
{
case sta_origin:
do_action(&STATETRANS(0)[event]);
break;
case sta_update:
do_action(&STATETRANS(1)[event]);
break;
case sta_updated:
do_action(&STATETRANS(2)[event]);
break;
case sta_wait:
do_action(&STATETRANS(3)[event]);
break;
default:
printf("state is invalid\n");
break;
}
}

总结

  • VAPS XT核心功能就是建立在状态机这个功能上面
  • 最终状态只有一个,Data Flow不允许Destination部分有相同实例
  • 带处理动作(Action)的状态机叫做ATN(Augmented Transition Network,增强转换网络),从名字就可以看出来它是用来补充状态机状态转换的。

低代码开发

VAPS XT的大部分操作可以通过官方Editor拖拽、调整属性完成,这就完成了80%的工作。

但是无法通过Editor拖拽、调整属性完成的部分,就需要使用代码来补充了。

因为基本控件是由VAPS XT提供了,设计人员只是进行复杂界面组合,生成代码功能也是由VAPS XT提供的。所以说VAPS XT是低代码开发软件。

需要代码开发的部分有

  • 数据流,即Data Flow
  • 响应函数,Transtitions
  • State Chart,这个是VAPS XT图形化状态机,它结合了数据流和响应函数

State Chart/Transtitions

State Chart一般和Trnastitions搭配使用,但是Transtitions可以完全取代State Chart

internal transtitions

在Action部分,我们可以实现复杂的处理逻辑,操作的语言为简化版的C。

你需要了解:

  • for/foreach循环,用于操作数组
  • if/else判断
  • var变量定义
  • when

这些都是C/C++/Python等等中最常用的关键字

Data Flow

data flow 数据流

数据流部分分为两部分:入口和出口。

入口可以重复,出口不可以重复。

下面是可以的

1
2
String.value -> Text.value
String.value -> Text2.value

下面的是不可以的

1
2
String.value -> Text.value
Text2.value -> Text.value

输入以后编辑器会报错。

数据流就是将控件/数据结构等的某个属性值赋值给另一个控件/数据结构等的某个属性值。

语法包括

  • IfElse 用于判断
  • 常用的函数比如:abs/ceil等等

演示

VAPS XT提供了预览功能,所谓的预览功能就是在设计时或者设计完成后,不编译就可查看运行效果的方式。

打开VAPS XT官方示例工程中的PFDExample

PFDExample

点击工具栏的Play按钮即可不编译就运行程序

play-pause-stop

仿真运行时,所有的数据接口处于灰色状态,并且不可修改。

play

同时,各个控件、模块的数值会根据Data Flow更新(PFDExample中没有状态图和Transition)。

VAPS XT只提供了这一种仿真功能,剩下的只能是编译运行了。

可以看到,预览过程中的各个状态的更新基本上就是状态机的在线演示。

说明

在工具栏有几个控件用于界面状态机开发

  • Initial Transition 入口点,有且只有一个
  • State 状态,程序某一个时刻会停留在某个状态
  • Transition 连接两个State,和internal transition效果一样
  • Guard 用于限制,相当于if判断
  • Choice 判断相当于switch/case
  • Note 注释
  • Connector 连接State和对应的Note
  • History 跟踪当前父状态中上一个嵌套的状态
  • Deep History 跟踪当前父状态中上一个嵌套的子状态
  • Link 一般是State与State间连接,Link可以连接State和某个State的子状态

问题

  • 部分功能是使用代码实现的,如何在不编译的情况下实现代码功能的运行。

下一篇:VAPS XT入门教程15.02.01:状态机与界面操作

技术交流群,欢迎加入讨论。这个圈子很小,大佬可能没兴趣加这些群聊社区之类的。所以只能带你入门,当然,欢迎大佬指导

qq 672991841


VAPS XT入门教程15.02:状态机
https://blog.jackeylea.com/vapsxt/state-machine-mechanism-of-vapsxt/
作者
JackeyLea
发布于
2022年3月7日
许可协议