如何在 gen_statem 中正确更新 state_timeout?

How do I correctly update state_timeout in gen_statem?

我需要编写一个实现 gen_statem 行为的模块。我需要模拟 ATM 工作,其中一项功能是如果此人在 10 秒内未按任何按钮,则将卡退还。 我必须声明:

waiting_for_card - 唯一可能的事件是插入卡片;插入卡时,我将状态更改为 waiting_for_pin,state_timeout = 10000ms,之后状态应自动更改为 waiting_for_card.

waiting_for_pin - 如果按下 number 按钮,我应该将数字放入输入列表并重新启动计时器,而 entercancel 按钮有不同的状态转换,应该只是取消计时器

这是代码片段:

waiting_for_card({call, From}, {insert, Card}, #state{accounts = Accounts}) ->
  case check_if_user_is_in_list(Card, Accounts) of
    true ->
      NewState = #state{
        accounts = Accounts,
        insertedCard = Card
      },
      {next_state, waiting_for_pin, NewState, [{reply, From, ok}, {state_timeout, 10000, timeout}]};
    false ->
      {keep_state_and_data, [{reply, From, {error, "ATM doesn't servise this card"}}]}
  end;

waiting_for_pin({call, From}, {button, cancel}, #state{accounts = Accounts}) ->
  NewState = #state{accounts = Accounts},
  {next_state, waiting_for_card, NewState, [{reply, From, {ok, "Card returned"}}, {state_timeout, cancel}]};

waiting_for_pin({call, From}, {button, enter}, #state{accounts = Accounts, input = Input, insertedCard = Card}) ->
  case check_pin(Card, lists:reverse(Input), Accounts) of
    true ->
      NewState = #state{
        accounts = Accounts,
        insertedCard = Card
      },
      {next_state, waiting_for_sum, NewState, [{reply, From, {ok, "Valid pin, enter the sum to withdraw"}}, {state_timeout, cancel}]};
    false ->
      NewState = #state{
        accounts = Accounts
      },
      {next_state, waiting_for_card, NewState, [{reply, From, {error, "Wrong pin, get your card"}}, {state_timeout, cancel}]}
  end;

waiting_for_pin(cast, {button, Button}, #state{accounts = Accounts, input = Input, insertedCard = Card}) ->
  NewState = #state{accounts = Accounts, input = [Button + 48 | Input], insertedCard = Card},
  {keep_state, NewState, [{state_timeout, cancel}, {state_timeout, 10000, timeout}]};

如你所见,现在我只是取消并在状态函数返回值的 Action 参数中开始一个新的超时。

模式{state_timeout、更新、EventContent} 没有重新启动计时器,我已经测试过了。

我是对的还是漏掉了什么?

I need to simulate ATM work and one of the features is to give the card back if the person doesn't push any button for 10 secs.
...
As you can see, now I just cancelling and start a new timeout in Action argument of returned value of state function.

根据 gen_statem 文档 state_timeout

Setting this timer while it is running will restart it with the new time-out value.

And,

enter_action() =
hibernate |
{hibernate, Hibernate :: hibernate()} |
timeout_action() |
reply_action()

These transition actions can be invoked by returning them from the state callback ...

...

Actions that set transition options override any previous of the same type... For example, the last event_timeout() overrides any previous event_timeout() in the list.

至于这个:

Pattern {state_timeout, update, EventContent} doesn't restart the timer is I have tested.

根据 timeout_update_action() 文档:

Updates a time-out with a new EventContent. See timeout_action() for how to start a time-out.

因此,update 只更新超时的 EventContent -- 而不是超时的时间。

这是您可以执行的操作的示例:

-module(card).
-behaviour(gen_statem).
-compile(export_all).

%client functions:
start_transaction() ->
    gen_statem:start_link({local, ?MODULE}, ?MODULE, no_args, []).

end_transaction() ->
    gen_statem:stop(?MODULE).

insert_card() ->
    gen_statem:call(?MODULE, card_inserted).

button(Number) ->
    gen_statem:call(?MODULE, {button, Number}).

% required callback functions
callback_mode() ->
    state_functions.

init(no_args) ->
    {ok, waiting_for_card, no_data}.

terminate(_Reason, _PreviousState, _Data) ->
    io:format("terminating..."),
    ok.


% waiting_for_card state:
waiting_for_card({call, From}, card_inserted, _Data) ->
    io:format("state waiting_for_card: call.~n"),
    {
        next_state, waiting_for_pin, _Pin=[], 
        [{reply, From, card_detected}, {state_timeout, 10000, return_card}] 
    }.

% waiting_for_pin state: 
waiting_for_pin(state_timeout, return_card, _Pin) ->
    io:format("state waiting_for_pin: state_timeout.~n"),
    io:format("Sorry, you took too long to enter your pin.~n"),
    io:format("Here is your card back.~n"), 
    {next_state, waiting_for_card, reset_pin};

waiting_for_pin({call, From}, {button, Button}, Pin) ->
    io:format("state waiting_for_pin: call.~n"),
    io:format("You pressed button: ~w.~n", [Button]),
    NewPin = [Button|Pin], %whatever
    {
        keep_state,
        NewPin,
        [{state_timeout, 10000, return_card}, {reply, From, got_number}]
    }.

这是 shell 中的测试 运行:

~/erlang_programs/gen_statem$ erl
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.3  (abort with ^G)

1> c(card).                 
card.erl:3: Warning: export_all flag enabled - all functions will be exported
{ok,card}

2> card:start_transaction().
{ok,<0.71.0>}

3> card:insert_card().      
state waiting_for_card: call.
card_detected
state waiting_for_pin: state_timeout.
Sorry, you took too long to enter your pin.
Here is your card back.

4> card:insert_card().
state waiting_for_card: call.
card_detected

5> card:button(4).          
state waiting_for_pin: call.
You pressed button: 4.
got_number
state waiting_for_pin: state_timeout.
Sorry, you took too long to enter your pin.
Here is your card back.

6> card:insert_card().
state waiting_for_card: call.
card_detected

7> card:button(4).    
state waiting_for_pin: call.
You pressed button: 4.
got_number

8> card:button(5).    
state waiting_for_pin: call.
You pressed button: 5.
got_number
state waiting_for_pin: state_timeout.
Sorry, you took too long to enter your pin.
Here is your card back.

9> ...waits here for 1 minute plus...