程序员

基于SNL的状态机

2020-12-09  本文已影响0人  七彩布谷

snl是面向状态的语言,作为一门语言,当然有自己的词法语法解析,这不是本文的范畴。同时,基于snl语言和epics-base开发的sequencer,集成到epics support中,给epics应用的流程控制提供支持。sequencer在ca框架下,属于client的部分,能独立运行,需要创建上下文、信道等惯例操作。

基本概念

基本语法

本文只针对sequencer结构,详尽的语法可以参考网站https://www-csr.bessy.de/control/SoftDist/sequencer/index.html

嵌入式代码

sequencer是基于snl语法的,是一种c-like的代码,而且可以调用嵌入的c代码,形式上是包含在百分号+括号内,如下所示。

{
    #action代码
    %{
        printError(ErrId, ErrLevel, FILE_AND_LINE_STRING, ": LayerSwitch\n");
    }%
}

同时也可以先声明后调用:

%{
    epicsInt32 transINT32(double value, double norminal)
    {
        return (epicsInt32)(((pow(2, 30) - 1) * value) / norminal);
    }
}%

{
    #action代码
    val = transINT32(6, 2.0);
}

需要特别注意的是,涉及pv的操作,要传入参数ssid,表示这个操作是在哪个状态集中完成的。ssid可以通过pvIndex获取,该调用不能嵌入到c代码中。
官方宣传是可以任何c/c++代码的,但本人在实际运用过程中,发现部分高级的是不支持的,索性直接用native的c代码好了,欢迎批评。

状态机

作为状态机,基本的要素包括:状态、事件、行为,简单的描述就是在某种状态,遇到某件事情,触发某个动作,并转移到另一状态。
下面的代码简单描述了snl状态机。

ss state_set_name
{
    state state1
    {
        entry
        {
            //action
        }
        
        when (event)
        {
            //action
        } state state2
        
        when (event)
        {
            //action
        } state state3
        
        exit
        {
            //action
        }
    }

    state state2
    {
        ...
    }
}
事件绑定

通常声明变量、pv关联、事件关联等操作如下:

int reqpowersupply; //声明变量
assign reqpowersupply to "G-ACS-PS:REQ-MRW"; //绑定pv
monitor reqpowersupply; //监视pv值,及时改变变量值
evflag powersupplyevt; //声明事件
sync reqpowersupply powersupplyevt; //绑定事件

这样一个关联pv的事件就定义好了,pv变量值得改变会直接改变事件变量,从而用efTest判定事件达成与否。相应地,efSet可以直接改变事件变量的值,efClear清除变量的值。

以上采用sync的方式绑定事件和变量,如果对于变化较快的pv值,可能无法及时处理事件的变化。因此使用syncQ将每次改变暂存在队列,需要的时候pvGetQ从队列头依次取出。

连接管理

通常在执行证实逻辑前,需要判定pv连接情况,包括分配数量、连接数量等。例如:

when (pvConnectCount() == pvAssignCount())
{
  ...
}
when (pvConnectCount() == pvChannelCount())
{
  ...
}

基本流程

编译过程

预编译

snc编译器读取.st或.stt文件,进行词法、语法解析,编译生成.c代码。本文不对编译过程和原理进行剖析,仅对生成结果进行解析。

/* Program table (global) */
seqProgram psctrl = {
    /* magic number */      2002005,
    /* program name */      "psctrl",
    /* channels */          seqg_chans,
    /* num. channels */     5829,
    /* state sets */        seqg_statesets,
    /* num. state sets */   2,
    /* user var size */     0,
    /* param */             "",
    /* num. event flags */  0,
    /* encoded options */   (0 | OPT_CONN | OPT_NEWEF),
    /* init func */         seqg_init,
    /* entry func */        0,
    /* exit func */         0,
    /* num. queues */       0
};

/* Variable declarations */
# 全局变量
# line 29 "../psctrl.st"
static  char tempstr[100];

# ss局部变量
struct seqg_vars_pscheck {
# line 1418 "../psctrl.st"
    int i;
} seqg_vars_pscheck;

# state局部变量
struct seqg_vars_psctrl {
# line 266 "../psctrl.st"
    struct {
# line 603 "../psctrl.st"
        int initall_dcon;
# line 604 "../psctrl.st"
        int initall_start;
# line 605 "../psctrl.st"
        int initall_done;
    } seqg_vars_st_InitAll;
} seqg_vars_psctrl;
/* Channel table */
static seqChan seqg_chans[] = {
    /* chName, offset, varName, varType, count, eventNum, efId, monitored, queueSize, queueIndex */
    {"", (size_t)&temppointlist[0], "temppointlist[0]", P_INT, 1, 1, 0, 0, 0, 0},
    {"G-ACS-SNS:RNUM-MRW", (size_t)&room_number, "room_number", P_INT, 1, 5002, 0, 1, 0, 0},
};
/* State set table */
static seqSS seqg_statesets[] = {
    {
    /* state set name */    "psctrl",
    /* states */            seqg_states_psctrl,
    /* number of states */  16
    },

    {
    /* state set name */    "pscheck",
    /* states */            seqg_states_pscheck,
    /* number of states */  2
    },
};
/* State table for state set "pscheck" */
static seqState seqg_states_pscheck[] = {
    {
    /* state name */        "st_Init",
    /* action function */   seqg_action_pscheck_1_st_Init,
    /* event function */    seqg_event_pscheck_1_st_Init,
    /* entry function */    seqg_entry_pscheck_1_st_Init,
    /* exit function */     0,
    /* event mask array */  seqg_mask_pscheck_1_st_Init,
    /* state options */     (0)
    },
    {
    /* state name */        "st_Monitor",
    /* action function */   seqg_action_pscheck_1_st_Monitor,
    /* event function */    seqg_event_pscheck_1_st_Monitor,
    /* entry function */    seqg_entry_pscheck_1_st_Monitor,
    /* exit function */     0,
    /* event mask array */  seqg_mask_pscheck_1_st_Monitor,
    /* state options */     (0)
    },
};
/* Entry function for state "st_Init" in state set "pscheck" */
static void seqg_entry_pscheck_1_st_Init(SS_ID seqg_env)
{
# line 1421 "../psctrl.st"
    printf("start ps check sequence\n");
}

seqg_event_执行条件检查时需要,对应语法为when。其中seqg_ptrn表示将要执行action的第几个case,seqg_pnst表示将要跳转到第几个状态。均为按照出现先后,从0计数。每个if为每个when的条件判定,从语法上也可以推断出各个条件之间有先后顺序。

/* Event function for state "st_Init" in state set "pscheck" */
static seqBool seqg_event_pscheck_1_st_Init(SS_ID seqg_env, int *seqg_ptrn, int *seqg_pnst)
{
# line 1424 "../psctrl.st"
    if (seq_pvConnectCount(seqg_env) == seq_pvAssignCount(seqg_env))
    {
        *seqg_pnst = 1;
        *seqg_ptrn = 0;
        return TRUE;
    }
# line 1427 "../psctrl.st"
    if (seq_delay(seqg_env, 1.0))
    {
        *seqg_pnst = 0;
        *seqg_ptrn = 1;
        return TRUE;
    }
    return FALSE;
}

seqg_action为各个条件下所需要执行的行为,各个case对应上文event不同的if分支。

/* Action function for state "st_Init" in state set "pscheck" */
static void seqg_action_pscheck_1_st_Init(SS_ID seqg_env, int seqg_trn, int *seqg_pnst)
{
    switch(seqg_trn)
    {
    case 0:
        {
        }
        return;
    case 1:
        {
        }
        return;
    }
}
编译

标准的gcc编译流程,不在赘述。

初始化流程

/* Program init func */
static void seqg_init(PROG_ID seqg_env)
{
}

接着,会对ss状态集中每个变量进行初始化。其中snc生成的变量按各个state区分开的,如下:

struct seqg_vars_psctrl {
# line 266 "../psctrl.st"
    int i;
# line 266 "../psctrl.st"
    int j;
# line 266 "../psctrl.st"
    int x;
    struct {
# line 603 "../psctrl.st"
        int initall_dcon;
# line 604 "../psctrl.st"
        int initall_start;
# line 605 "../psctrl.st"
        int initall_done;
    } seqg_vars_st_InitAll;
}seqg_vars_psctrl;

其中ijx声明在ss,initall_dconinitall_startinitall_done声明在状态st_InitAll里面,作用域会有所不同。

接着,对所有的pv进行connect,这步操作过程可参考《CA工作机制》。而线程会循环等待,直到所有结果返回。如果状态错误,那么会进入退出sequencer流程,断掉连接,释放资源等。

接着,如果program有entryFunc就会执行,不过一般entryFuncexitFunc均为空。当然也可以利用这一时机,处理很多逻辑上的资源分配、释放问题。

最后,为每个ss激发线程,使其在独立线程中运行,而主线程会直接接管第一个ss,并跳转到入口ss_entry。这是整个状态机的主循环,处理ss所有的事件信息。

主循环

主循环是游戏里面的概念,逻辑上是个死循环,在循环体内每次都要更新信息、处理事件等。在sequencer中,也是在while大循环中更新pv数据、处理事件请求,并执行相应的状态跳转。


总结下来一句话,基于snl的sequencer,依赖MainLoop处理各种事件和变量的更新,状态机通过状态表和指针的切换完成。整体结构和实现细节,还是十分值得深入学习的。

上一篇下一篇

猜你喜欢

热点阅读