scxml-3

编译源码:

预先安装如下:

sudo apt-get install build-essential libboost-all-dev cmake autorevision xsltproc

autorevision用于更新scxml的版本;

xsltproc用于编译单元测试;

编译:

mkdir build
cd build
cmake ..
make -j4
make install

To help with auto-generating state-machines in cmake projects, an scxml_generator.cmake module is installed. The examples demonstrate how this can be used.

To build unit tests, and examples run cmake with

mkdir build
cd build
cmake -DDEVELOPER=1 ..
make -j4
make test

下面看一下例子:

第一个Hello World:

先看图

 

 

 是一个由一个无条件转移相连接的两个状态组成;

上面的图是由这里的软件来实现的:https://github.com/fmorbini/scxmlgui/

 java -jar **.jar (在windows下执行,**.jar为https://github.com/fmorbini/scxmlgui/blob/master/extra/fsm-editor.jar

通过scxmlgui绘制之后,保存生成一个.scxml文件,内容如下:

<scxml initial="hello" version="0.9" xmlns="http://www.w3.org/2005/07/scxml">
 <state id="hello">
  <transition target="world"></transition>
 </state>
 <state id="world"></state>
</scxml>

然后通过上面编译的scxmlcc来将.scxml生成.h文件

scxmlcc -o hello_world.h hello_world.scxml

自定义的行为可以用c++代码来实现,例如在进入和退出的时候的一些动作:

#include "hello_world.h"
#include <iostream>

using namespace std;

typedef sc_hello_world sc;

template<> void sc::state_actions<sc::state_hello>::enter(sc::data_model &m)
{
    cout << "hello" << endl;    
}

template<> void sc::state_actions<sc::state_world>::enter(sc::data_model &m)
{
    cout << "world" << endl;    
}

int main(int argc, char *argv[])
{
    sc sc0;
    sc.init();
    return 0;
}

执行之后,会输出如下:

hello
world

init方法初始化整个状态机并进入到它的初始状态;

第二个例子:

Timer Switch:实现了一个简单的定时器表

 

 其大意是,该表可以通过发送一个button事件到状态机来在on或者off之间切换。在on状态的时候,当定时截止之后,会进入到off状态。

在on状态中,一个timer事件来计数timer的值。unconditional事件从on到off需要一个条件,因此只有当timer>=5之后,才会被执行,最后off状态会把counter置为0;

虚点的timer转移意味着,其是一个无目标的转移。意味着在on状态的enter和exit行为都不会被执行当timer事件发生的时候。行为的实现如下:

template<> void sc::state_actions<sc::state_on>::enter(sc::data_model &m)
{
    cout << "on" << endl;    
}

template<> void sc::state_actions<sc::state_off>::enter(sc::data_model &m)
{
    cout << "off" << endl;    
    m.user->timer = 0;
}

template<> bool sc::transition_actions<&sc::state::unconditional, sc::state_on, sc::state_off>::condition(sc::data_model &m)
{
    return m.user->timer >= 5;
}

template<> void sc::transition_actions<&sc::state::event_timer, sc::state_on>::enter(sc::data_model &m)
{
    ++m.user->timer;
}

对于本例子,一个数据模型必须定义来包含一个timer变量,生成的状态机类有一个user_model结构体做了前向声明,因此一个自定义的数据模型如下:

struct sc::user_model 
{
    int timer;
};

 

自定义的数据模型被构造之后,然后传递到状态机的构造函数中:

sc::user_model m;
sc sc(&m);
sc.init();

完整代码如下:

timer_switch.cpp

/*************************************************************************
 ** Copyright (C) 2013 Jan Pedersen <jp@jp-embedded.com>
 ** 
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 ** the Free Software Foundation, either version 3 of the License, or
 ** (at your option) any later version.
 ** 
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ** GNU General Public License for more details.
 ** 
 ** You should have received a copy of the GNU General Public License
 ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *************************************************************************/

#include "timer_switch.h"
#include "termio.h"
#include <iostream>

using namespace std;

typedef sc_timer_switch sc;

struct sc::user_model 
{
    int timer;
};

template<> void sc::state_actions<sc::state_on>::enter(sc::data_model &m)
{
    cout << "on" << endl;    
}

template<> void sc::state_actions<sc::state_off>::enter(sc::data_model &m)
{
    cout << "off" << endl;    
    m.user->timer = 0;
}

template<> bool sc::transition_actions<&sc::state::unconditional, sc::state_on, sc::state_off>::condition(sc::data_model &m)
{
    cout << "unconditional: timer is " <<m.user->timer << endl;
    return m.user->timer >= 5;
}

template<> void sc::transition_actions<&sc::state::event_timer, sc::state_on>::enter(sc::data_model &m)
{
    ++m.user->timer;
    cout << "timer is " <<m.user->timer <<endl;
}

int main(int argc, char *argv[])
{
    // set unbuffered input and disable echo
    termio t;
    t.echo(false);
    t.canonical(false);

    cout << "input:  b = button event" << endl;
          cout << "        t = timer event" << endl;
    cout << "        q = quit" << endl;

    sc::user_model m;
    sc sc(&m);
    sc.init();

    while(true) {
        char c;    
        cin >> c;
        cout << "c is " << c << endl;
        sc::event e = NULL;
        switch (c) {
            case 'b': e = &sc::state::event_button; break;
            case 't': e = &sc::state::event_timer;  break;
            case 'q': return 0;
            default: continue;//e = &sc::state::unconditional;
        }
        sc.dispatch(e);
    }
    return 0;
}
View Code

timer_switch.h

// This file is automatically generated by scxmlcc (version 0.9M-2)
// For more information, see http://scxmlcc.org

#ifndef __SC_TIMER_SWITCH
#define __SC_TIMER_SWITCH

#include <deque>
#include <vector>
#include <string>

class sc_timer_switch
{
    public:

    struct data_model;
    struct user_model;

    class state
    {
        public:
        virtual ~state(){}
        virtual state* event_button(sc_timer_switch&) { return 0; }
        virtual state* event_timer(sc_timer_switch&) { return 0; }
        virtual state* unconditional(sc_timer_switch&) { return 0; }
        virtual state* initial(sc_timer_switch&) { return 0; }

        template<class T> void enter(data_model&, ...) {}
        template<class T> void exit(data_model&, ...) {}
        virtual bool in(const void*) { return false; }
    };

    typedef state* (state::*event)(sc_timer_switch&);

    template<class C> class state_actions
    {
        protected:
        void enter(data_model&) {} // default enter action
        void exit(data_model&) {} // default exit action
    };

    template<class C, class P> class composite : public P, public state_actions<C>
    {
        virtual state* initial(sc_timer_switch&) { return 0; }
        public:
        typedef P parent_t;
        // LCA calculation
        template<class T> void enter(data_model&, composite*) {}
        template<class T> void enter(data_model &m, ...) { P::template enter<T>(m, static_cast<T*>(nullptr)); state_actions<C>::enter(m); }
        template<class T> void exit(data_model&, composite*) {}
        template<class T> void exit(data_model &m, ...) { state_actions<C>::exit(m); P::template exit<T>(m, static_cast<T*>(nullptr)); }
        static const void* id() { static const struct{} _id; return &_id; }
        bool in(const void *si) { return (si == id() || P::in(si)); }
    };

    class no_state {};
    enum transition_type { external, internal };

    template<event E, class S, class D0 = no_state> class transition_actions
    {
        protected:
        void enter(data_model&) {} // default enter action
        bool condition(data_model&) { return true; } // default condition action
    };

    // external/internal transition
    template<event E, class S, class D = no_state, transition_type T = external> class transition : public transition_actions<E, S, D>
    {
        template<transition_type I> struct id { };
        void state_enter(D* d, data_model &m, id<internal>, S*) { d->template enter<composite<S, typename S::parent_t> >(m); } // internal transition, where dst is descendant of src
        void state_enter(D* d, data_model &m, ...) { d->template enter<typename S::parent_t>(m); } // external transition, or dst is not descendant of src
        void state_exit(S*, data_model &, id<internal>, S*) {} // internal transition, where dst is descendant of src
        void state_exit(S* s, data_model &m, ...) { s->template exit<typename D::parent_t>(m); } // external transition, or dst is not descendant of src
        public:
        state* operator ()(S *s, sc_timer_switch &sc)
        {
            if(!transition_actions<E, S, D>::condition(sc.model)) return 0;
            D *d = sc.get_state<D>();
            state_exit(s, sc.model, id<T>(), static_cast<typename D::parent_t*>(nullptr));
            transition_actions<E, S, D>::enter(sc.model);
            state_enter(d, sc.model, id<T>(), static_cast<typename D::parent_t*>(nullptr));
            return d;
        }
    };

    // transition with no target
    template<event E, class S> class transition<E, S, no_state> : public transition_actions<E, S, no_state>
    {
        public:
        state* operator ()(S *s, sc_timer_switch &sc)
        {
            if(!transition_actions<E, S, no_state>::condition(sc.model)) return 0;
            transition_actions<E, S, no_state>::enter(sc.model);
            return s;
        }
    };

    private: bool dispatch_event(event e)
    {
        state *next_state;
        if ((next_state = (model.cur_state->*e)(*this))) model.cur_state = next_state;
        return !!next_state;
    }

    public: void dispatch(event e = &state::unconditional)
    {
        bool cont = dispatch_event(e) || dispatch_event(&state::unconditional);
        while (cont) {
            if ((cont = dispatch_event(&state::initial)));
            else if ((cont = dispatch_event(&state::unconditional)));
            else if (model.event_queue.size()) cont = dispatch_event(model.event_queue.front()), model.event_queue.pop_front(), cont |= !model.event_queue.empty();
            else break;
        }
    }
    struct data_model
    {
        std::deque<event> event_queue;
        state *cur_state;
        template <class S> bool In() { return cur_state->in(S::id()); }
        user_model *user;
        const std::string _sessionid;
        const std::string _name;
        data_model(user_model* um)
        : user(um)
        , _sessionid(std::to_string(reinterpret_cast<long long unsigned int>(this)))
        , _name("timer_switch")
        {}
    } model;

    sc_timer_switch(user_model *user = nullptr)
    : model(user)
    {
        model.cur_state = get_state<scxml>();
    }

    void init() { dispatch(&state::initial); }

    struct scxml : public composite<scxml, state>
    {
        state* initial(sc_timer_switch&sc) { return transition<&state::initial, scxml, state_off, internal>()(this, sc); }
    };

    struct state_off : public composite<state_off, scxml>
    {
        state* event_button(sc_timer_switch &sc) { return transition<&state::event_button, state_off, state_on>()(this, sc); }
    };

    struct state_on : public composite<state_on, scxml>
    {
        state* event_button(sc_timer_switch &sc) { return transition<&state::event_button, state_on, state_off>()(this, sc); }
        state* event_timer(sc_timer_switch &sc) { return transition<&state::event_timer, state_on>()(this, sc); }
        state* unconditional(sc_timer_switch &sc) { return transition<&state::unconditional, state_on, state_off>()(this, sc); }
    };

    template<class T> T* get_state()
    {
        static T t;
        return &t;
    }
};

#endif
View Code

第三个例子:

售卖机器:

本售卖机器可以分配三种类型的coke,在用于插入货币到机器之后。如下图所示:

 

 状态机负责整个逻辑;

Coin Sensor负责检测货币的插入;

keypad负责检测按键的按下;

Display负责显示文本消息;

Coin refund负责退换硬币给用户;

Dispenser负责分配三种不同类型的coke给用户;

Input simulator负责从你的PC的键盘中读取输入,并触发货币传感器和键盘pad以便仿真。

不同组件之间的通信是通过signaling机制;其目的是为了解耦模块之间的依赖;因此单独的模块不知道彼此;

状态机的实现如下所示:

 

在初始化之后,状态机会进入到collect_coins状态;然后等待货币的插入;当货币插入之后,一个在用户模型user_model中的变量credit通过事件N和D来递增。当zero,coke或者diet按钮在机器上被按下时,对应的事件zero,coke或者diet被触发。这三个transitions到状态dispense_*状态,有一个条件就是credit必须是大于等于对应的coke的价格。如果条件满足,状态机会转移到状态dispense_*state,对应的coke就会被分配,对应的credit会减少。这三个dispense状态是最后的状态,因此他们会触发一个done,状态机会转移到coin_return状态。当在active状态时,如果cancel按钮被按下,对应的cancel事件被触发,状态机也会转移到状态coin_return.

在coin_return状态中,状态机会进入到return_d状态。这个状态有两个条件转移。当credit的值小于一角硬币时,会进入到状态return_n,当credit的值大于等于dime时,这个转移会触发coin refund组件来退换一个dime。并返回到return_d,return_n状态类似;当credi为0时,return_done状态进入,且是最后的状态,会触发一个done事件,状态机会退回到init状态。

 

posted on 2021-05-08 17:30  gary_123  阅读(103)  评论(0编辑  收藏  举报

导航