From 3ed7d729cab697b9f668dadb563d629de10f593d Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
As
Like most
See section
-
@@ -401,8 +404,8 @@ State(S) x Event(E) -> Actions(A), State(S')
In the first
There are more specific state-transition actions
- that a callback function can order the
Since the state enter call is not an event there are restrictions
on the allowed return value and
-
In state
10,000 is a time-out value in milliseconds.
After this time (10 seconds), a time-out occurs.
@@ -1024,7 +1028,7 @@ handle_common({call,From}, code_length, #{code := Code} = Data) ->
Another way to do it is through a convenience macro
-
This example uses
@@ -1059,6 +1063,14 @@ open(...) -> ... ;
when you want to stay in the current state but do not know or
care about what it is.
+ If the common event handler needs to know the current state
+ a function
io:format("Lock~n", []).
do_unlock() ->
@@ -925,7 +929,7 @@ locked(
+ ]]>
code_length() ->
gen_statem:call(?NAME, code_length).
--define(HANDLE_COMMON(T, C, D),
- ?FUNCTION_NAME(T, C, D) -> handle_common((T), (C), (D))).
+-define(HANDLE_COMMON,
+ ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
%%
handle_common({call,From}, code_length, #{code := Code} = Data) ->
{keep_state, Data, [{reply,From,length(Code)}]}.
@@ -1047,7 +1051,7 @@ locked(...) -> ... ;
...
open(...) -> ... ;
?HANDLE_COMMON.
- ]]>
+]]>
handle_common(T, C, ?FUNCTION_NAME, D)).
+ ]]>
When ordered to shut down, the
This makes the
It is ordered by the state transition action
-
- This type of time-out is useful to for example act on inactivity.
+ This type of time-out is useful for example to act on inactivity.
Let us restart the code sequence
if no button is pressed for say 30 seconds:
Whenever we receive a button event we start an event time-out
of 30 seconds, and if we get an event type
Here is how to accomplish the state time-out
in the previous example by instead using a generic time-out
- named
- Just as
-
- Another way to handle a late time-out can be to not cancel it,
- but to ignore it if it arrives in a state
- where it is known to be late.
+ In this particular case we do not need to cancel the timeout
+ since the timeout event is the only possible reason to
+ change the state from
+ Instead of bothering with when to cancel a time-out,
+ a late time-out event can be handled by ignoring it
+ if it arrives in a state where it is known to be late.
% Correct
do_unlock(),
{next_state, open, Data#{buttons := []},
- [{{timeout,open_tm},10000,lock}]};
+ [{{timeout,open},10000,lock}]};
...
-open({timeout,open_tm}, lock, Data) ->
+open({timeout,open}, lock, Data) ->
do_lock(),
{next_state,locked,Data};
open(cast, {button,_}, Data) ->
{keep_state,Data};
...
- ]]>
+]]>
The most versatile way to handle time-outs is to use
Erlang Timers; see
-
Removing the
Postponing is ordered by the state transition
-
@@ -1392,14 +1411,17 @@ open(cast, {button,_}, Data) -> open(cast, {button,_}, Data) -> {keep_state,Data,[postpone]}; ... - ]]> +]]>
Since a postponed event is only retried after a state change,
you have to think about where to keep a state data item.
You can keep it in the server
- The state transition
-
You can repeat the state enter code by returning one of
It can sometimes be beneficial to be able to generate events
to your own state machine.
- This can be done with the state transition
-
@@ -1643,10 +1669,10 @@ locked(
internal, {button,Digit},
#{code := Code, length := Length, buttons := Buttons} = Data) ->
...
- ]]>
+]]>
- {keep_state, Data#{button := Button};
+ {keep_state, Data#{button := Button}};
handle_common(cast, {up,Button}, Data) ->
case Data of
#{button := Button} ->
@@ -1660,7 +1686,7 @@ handle_common(cast, {up,Button}, Data) ->
open(internal, {button,_}, Data) ->
{keep_state,Data,[postpone]};
...
- ]]>
+]]>
If you start this program with
Notice that this state diagram does not specify how to handle
a button event in the state
process_flag(trap_exit, true),
- Data = #{code => Code, length => Length, buttons => []},
+ Data = #{code => Code, length => length(Code), buttons => []},
{ok, locked, Data}.
callback_mode() ->
[state_functions,state_enter].
-define(HANDLE_COMMON,
- ?FUNCTION_NAME(T, C, D) -> handle_common((T), (C), (D))).
+ ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
%%
handle_common(cast, {down,Button}, Data) ->
{keep_state, Data#{button => Button}};
@@ -1763,14 +1789,13 @@ locked(
if
NewButtons =:= Code -> % Correct
do_unlock(),
- {next_state, open, Data,
- [{state_timeout,10000,lock}]};
+ {next_state, open, Data};
true -> % Incomplete | Incorrect
{keep_state, Data#{buttons := NewButtons},
[{state_timeout,30000,button}]}
end;
?HANDLE_COMMON.
- ]]>
+]]>
do_unlock(),
@@ -1789,7 +1814,7 @@ do_unlock() ->
terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
- ]]>
+ ]]>
+
[handle_event_function,state_enter].
-
+ ]]>
+
do_lock(),
@@ -1829,14 +1855,13 @@ handle_event(
if
NewButtons =:= Code -> % Correct
do_unlock(),
- {next_state, open, Data,
- [{state_timeout,10000,lock}]};
+ {next_state, open, Data};
true -> % Incomplete | Incorrect
{keep_state, Data#{buttons := NewButtons},
[{state_timeout,30000,button}]}
end;
]]>
-
@@ -1844,12 +1869,11 @@ handle_event(enter, _OldState, open, _Data) ->
{keep_state_and_data, [{state_timeout,10000,lock}]};
handle_event(state_timeout, lock, open, Data) ->
{next_state, locked, Data};
-handle_event(cast, {button,_}, open, _) ->
+handle_event(internal, {button,_}, open, _) ->
{keep_state_and_data,[postpone]};
]]>
-
{keep_state, Data#{button => Button}};
handle_event(cast, {up,Button}, _State, Data) ->
@@ -1862,13 +1886,11 @@ handle_event(cast, {up,Button}, _State, Data) ->
end;
handle_event({call,From}, code_length, _State, #{length := Length}) ->
{keep_state_and_data, [{reply,From,Length}]}.
-
-...
- ]]>
+ ]]>
- Notice that postponing buttons from the
Suppose now that we call
- So we make the
- If a process now calls
code_lock:start_link([a,b,c], x).
+{ok,<0.666.0>}
+2> code_lock:button(a).
+ok
+3> code_lock:button(b).
+ok
+4> code_lock:button(c).
+ok
+Open
+5> code_lock:button(y).
+ok
+6> code_lock:set_lock_button(y).
+x
+% What should happen here? Immediate lock or nothing?
+]]>
+ + We could say that the button was pressed too early + so it is not to be recognized as the lock button. + Or we can make the lock button part of the state so + when we then change the lock button in the locked state, + the change becomes a state change + and all postponed events are retried, + therefore the lock is immediately locked!
We define the state as
- case {EventType, EventContent} of
- {enter, _OldState} ->
- do_lock(),
- {keep_state, Data#{buttons := []}};
- {state_timeout, button} ->
- {keep_state, Data#{buttons := []}};
- {{call,From}, {button,Digit}} ->
- #{length := Length, buttons := Buttons} = Data,
- NewButtons =
- if
- length(Buttons) < Length ->
- Buttons;
- true ->
- tl(Buttons)
- end ++ [Button],
- case Data of
- #{code := NewButtons} ->
- {next_state, {open,LockButton}, Data,
- [{reply,From,ok}]};
- #{} ->
- {keep_state, Data#{buttons := NewButtons},
- [{reply,From,ok},
- {state_timeout,30000,button}]}
- end
- end;
+%% State: open
+handle_event(enter, _OldState, {open,_}, _Data) ->
+ do_unlock(),
+ {keep_state_and_data,
+ [{state_timeout,10000,lock}]};
+handle_event(state_timeout, lock, {open,_}, Data) ->
+ {next_state, locked, Data};
+handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
+ {next_state, {locked,LockButton}, Data};
+handle_event(cast, {button,_}, {open,_}, Data) ->
+ {keep_state_and_data,[postpone]};
]]>
- case {EventType, EventContent} of
- {enter, _OldState} ->
- do_unlock(),
- {keep_state_and_data,
- [{state_timeout,10000,lock}]};
- {state_timeout, lock} ->
- {next_state, {locked,LockButton}, Data};
- {{call,From}, {button,Digit}} ->
- if
- Digit =:= LockButton ->
- {next_state, {locked,LockButton}, Data,
- [{reply,From,locked}]};
- true ->
- {keep_state_and_data,
- [postpone]}
- end
- end.
+ {call,From}, {set_lock_button,NewLockButton},
+ {StateName,OldLockButton}, Data) ->
+ {next_state, {StateName,NewLockButton}, Data,
+ [{reply,From,OldLockButton}]}.
]]>
@@ -2099,27 +2102,7 @@ do_unlock() ->
terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
-format_status(Opt, [_PDict,State,Data]) ->
- StateData =
- {State,
- maps:filter(
- fun (code, _) -> false;
- (remaining, _) -> false;
- (_, _) -> true
- end,
- Data)},
- case Opt of
- terminate ->
- StateData;
- normal ->
- [{data,[{"State",StateData}]}]
- end.
]]>
-
- It can be an ill-fitting model for a physical code lock
- that the
- case {EventType, EventContent} of
- {enter, _OldState} ->
- do_unlock(),
- {keep_state_and_data,
- [{state_timeout,10000,lock},
- hibernate]};
+handle_event(enter, _OldState, {open,_}, _Data) ->
+ do_unlock(),
+ {keep_state_and_data,
+ [{state_timeout,10000,lock},
+ hibernate]};
...
- ]]>
+]]>
The atom
To change that we would need to insert
action
@@ -2201,7 +2180,8 @@ handle_event( This particular server probably does not use heap memory worth hibernating for. To gain anything from hibernation, your server would - have to produce some garbage during callback execution, + have to produce non-insignificant garbage + during callback execution, for which this example server can serve as a bad example.
-- cgit v1.2.3