From 6ee0aefd8a0ea9c165211c42d5244182b5aa9210 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 13 Sep 2016 14:00:04 +0200 Subject: Implement state entry events --- lib/stdlib/doc/src/gen_statem.xml | 82 ++++++++++++- lib/stdlib/src/gen_statem.erl | 208 +++++++++++++++++++++++--------- lib/stdlib/test/gen_statem_SUITE.erl | 66 +++++++++- system/doc/design_principles/statem.xml | 152 +++++++++++++++++------ 4 files changed, 400 insertions(+), 108 deletions(-) diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 17f1526a21..a4c5438a08 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -70,6 +70,7 @@ The state can be any term. Events can be postponed. Events can be self-generated. + Automatic state entry events can be generated. A reply can be sent from a later state. There can be multiple sys traceable replies. @@ -193,6 +194,12 @@ erlang:'!' -----> Module:StateName/3 gen_fsm to force processing an inserted event before others.

+

+ The gen_statem engine can automatically insert + a special event whenever a new state is entered; see + state_entry_mode(). + This makes it easy to handle code common to all state entries. +

If you in gen_statem, for example, postpone an event in one state and then call another state function @@ -515,7 +522,7 @@ handle_event(_, _, State, Data) -> Type info originates from regular process messages sent to the gen_statem. Also, the state machine implementation can generate events of types - timeout and internal to itself. + timeout, enter and internal to itself.

@@ -550,6 +557,34 @@ handle_event(_, _, State, Data) -> + + + +

+ The state entry mode is selected when starting the + gen_statem and after code change + using the return value from + Module:callback_mode/0. +

+

+ If + Module:callback_mode/0 + returns a list containing state_entry_events, + the gen_statem engine will, at every state change, + insert an event of type + enter + with content OldState. This event will be inserted + before all other events such as those generated by + action() + next_event. +

+

+ If + Module:callback_mode/0 + does not return such a list, no state entry events are inserted. +

+
+
@@ -590,6 +625,16 @@ handle_event(_, _, State, Data) -> all other events.

+ +

+ If the state changes or is the initial state, and the + state entry mode + is state_entry_events, an event of type + enter + with content OldState is inserted + to be processed before all other events including those above. +

+

If an @@ -1288,7 +1333,9 @@ handle_event(_, _, State, Data) -> CallbackMode = - callback_mode() + callback_mode() | + [ callback_mode() + | state_entry_events ] @@ -1313,12 +1360,35 @@ handle_event(_, _, State, Data) -> Module:code_change/4 returns.

+

+ The CallbackMode is either just + callback_mode() + or a list containing + callback_mode() + and possibly the atom + state_entry_events. +

+

+ If the atom state_entry_events is present in the list, + the gen_statem engine will, at every state change, + insert an event of type + enter + with content OldState. This event will be inserted + before all other events such as those generated by + action() + next_event. +

+

+ No state entry event will be inserted after a + Module:code_change/4 + since transforming the state to a newer version is regarded + as staying in the same state even if the newer version state + should have a different name. +

- If this function's body does not consist of solely one of two - possible - atoms - the callback module is doing something strange. + If this function's body does not return an inline constant + value the callback module is doing something strange.

diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 46c0e92a9b..7f437404ed 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -53,7 +53,7 @@ action/0]). %% Fix problem for doc build --export_type([transition_option/0]). +-export_type([state_entry_mode/0,transition_option/0]). %%%========================================================================== %%% Interface functions. @@ -72,9 +72,10 @@ -type event_type() :: {'call',From :: from()} | 'cast' | - 'info' | 'timeout' | 'internal'. + 'info' | 'timeout' | 'enter' | 'internal'. -type callback_mode() :: 'state_functions' | 'handle_event_function'. +-type state_entry_mode() :: 'state_entry_events'. -type transition_option() :: postpone() | hibernate() | event_timeout(). @@ -183,7 +184,9 @@ %% %% It is called once after init/0 and code_change/4 but before %% the first state callback StateName/3 or handle_event/4. --callback callback_mode() -> callback_mode(). +-callback callback_mode() -> + callback_mode() | + [callback_mode() | state_entry_mode()]. %% Example state callback for StateName = 'state_name' %% when callback_mode() =:= state_functions. @@ -556,19 +559,27 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> end, S = #{ callback_mode => undefined, + state_entry_events => false, module => Module, name => Name, - %% All fields below will be replaced according to the arguments to + %% The rest of the fields are set from to the arguments to %% loop_event_actions/10 when it finally loops back to loop/3 - state => State, - data => Data, - postponed => P, - hibernate => false, - timer => undefined}, + %% in loop_events_done/8 + %% + %% Marker for initial state, cleared immediately when used + init_state => true + }, NewDebug = sys_debug(Debug, S, State, {enter,Event,State}), - loop_event_actions( - Parent, NewDebug, S, Events, - State, Data, P, Event, State, NewActions). + case call_callback_mode(S) of + {ok,NewS} -> + loop_event_actions( + Parent, NewDebug, NewS, Events, + State, Data, P, Event, State, NewActions); + {Class,Reason,Stacktrace} -> + terminate( + Class, Reason, Stacktrace, + NewDebug, S, [Event|Events], State, Data, P) + end. %%%========================================================================== %%% gen callbacks @@ -866,13 +877,93 @@ loop_events( loop_events_done(Parent, Debug, S, Timer, State, Data, P, Hibernate) -> NewS = S#{ - state := State, - data := Data, - postponed := P, - hibernate := Hibernate, - timer := Timer}, + state => State, + data => Data, + postponed => P, + hibernate => Hibernate, + timer => Timer}, loop(Parent, Debug, NewS). + + +parse_callback_mode([], CBMode, SEntry) -> + {CBMode,SEntry}; +parse_callback_mode([H|T], CBMode, SEntry) -> + case callback_mode(H) of + true -> + parse_callback_mode(T, H, SEntry); + false -> + case H of + state_entry_events -> + parse_callback_mode(T, CBMode, true); + _ -> + {undefined,SEntry} + end + end; +parse_callback_mode(_, _CBMode, SEntry) -> + {undefined,SEntry}. + +call_callback_mode(S, CallbackMode) -> + case + parse_callback_mode( + if + is_atom(CallbackMode) -> + [CallbackMode]; + true -> + CallbackMode + end, undefined, false) + of + {undefined,_} -> + {error, + {bad_return_from_callback_mode,CallbackMode}, + ?STACKTRACE()}; + {CBMode,SEntry} -> + {ok, + S#{ + callback_mode := CBMode, + state_entry_events := SEntry}} + end. + +call_callback_mode(#{module := Module} = S) -> + try Module:callback_mode() of + CallbackMode -> + call_callback_mode(S, CallbackMode) + catch + CallbackMode -> + call_callback_mode(S, CallbackMode); + error:undef -> + %% Process undef to check for the simple mistake + %% of calling a nonexistent state function + %% to make the undef more precise + case erlang:get_stacktrace() of + [{Module,callback_mode,[]=Args,_} + |Stacktrace] -> + {error, + {undef_callback,{Module,callback_mode,Args}}, + Stacktrace}; + Stacktrace -> + {error,undef,Stacktrace} + end; + Class:Reason -> + {Class,Reason,erlang:get_stacktrace()} + end. + +loop_event( + Parent, Debug, + #{callback_mode := undefined} = S, + Events, + State, Data, P, Event, Hibernate) -> + %% This happens after code_change/4 + case call_callback_mode(S) of + {ok,NewS} -> + loop_event( + Parent, Debug, NewS, Events, + State, Data, P, Event, Hibernate); + {Class,Reason,Stacktrace} -> + terminate( + Class, Reason, Stacktrace, + Debug, S, [Event|Events], State, Data, P) + end; loop_event( Parent, Debug, #{callback_mode := CallbackMode, @@ -891,24 +982,16 @@ loop_event( %% try case CallbackMode of - undefined -> - Module:callback_mode(); state_functions -> erlang:apply(Module, State, [Type,Content,Data]); handle_event_function -> Module:handle_event(Type, Content, State, Data) end of - Result when CallbackMode =:= undefined -> - loop_event_callback_mode( - Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) catch - Result when CallbackMode =:= undefined -> - loop_event_callback_mode( - Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result); @@ -936,14 +1019,6 @@ loop_event( %% of calling a nonexistent state function %% to make the undef more precise case erlang:get_stacktrace() of - [{Module,callback_mode,[]=Args,_} - |Stacktrace] - when CallbackMode =:= undefined -> - terminate( - error, - {undef_callback,{Module,callback_mode,Args}}, - Stacktrace, - Debug, S, [Event|Events], State, Data, P); [{Module,State,[Type,Content,Data]=Args,_} |Stacktrace] when CallbackMode =:= state_functions -> @@ -972,25 +1047,6 @@ loop_event( Debug, S, [Event|Events], State, Data, P) end. -%% Interpret callback_mode() result -loop_event_callback_mode( - Parent, Debug, S, Events, State, Data, P, Event, CallbackMode) -> - case callback_mode(CallbackMode) of - true -> - Hibernate = false, % We have already GC:ed recently - loop_event( - Parent, Debug, - S#{callback_mode := CallbackMode}, - Events, - State, Data, P, Event, Hibernate); - false -> - terminate( - error, - {bad_return_from_callback_mode,CallbackMode}, - ?STACKTRACE(), - Debug, S, [Event|Events], State, Data, P) - end. - %% Interpret all callback return variants loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) -> @@ -1174,7 +1230,7 @@ loop_event_actions( %% %% End of actions list loop_event_actions( - Parent, Debug, S, Events, + Parent, Debug, #{state_entry_events := SEEvents} = S, Events, State, NewData, P0, Event, NextState, [], Postpone, Hibernate, Timeout, NextEvents) -> %% @@ -1196,7 +1252,25 @@ loop_event_actions( {lists:reverse(P1, Events),[]} end, %% Place next events first in queue - Q = lists:reverse(NextEvents, Q2), + Q3 = lists:reverse(NextEvents, Q2), + %% State entry events + Q = + case SEEvents of + true -> + %% Generate state entry events + case + (NextState =/= State) + orelse maps:is_key(init_state, S) + of + true -> + %% State change or initial state + [{enter,State}|Q3]; + false -> + Q3 + end; + false -> + Q3 + end, %% NewDebug = sys_debug( @@ -1208,7 +1282,15 @@ loop_event_actions( {consume,Event,NextState} end), loop_events( - Parent, NewDebug, S, Q, NextState, NewData, P, Hibernate, Timeout). + Parent, NewDebug, + %% Avoid infinite loop in initial state with state entry events + case maps:is_key(init_state, S) of + true -> + maps:remove(init_state, S); + false -> + S + end, + Q, NextState, NewData, P, Hibernate, Timeout). %%--------------------------------------------------------------------------- %% Server helpers @@ -1285,7 +1367,9 @@ terminate( error_info( Class, Reason, Stacktrace, - #{name := Name, callback_mode := CallbackMode}, + #{name := Name, + callback_mode := CallbackMode, + state_entry_events := SEEvents}, Q, P, FmtData) -> {FixedReason,FixedStacktrace} = case Stacktrace of @@ -1312,6 +1396,13 @@ error_info( end; _ -> {Reason,Stacktrace} end, + CBMode = + case SEEvents of + true -> + [CallbackMode,state_entry_events]; + false -> + CallbackMode + end, error_logger:format( "** State machine ~p terminating~n" ++ case Q of @@ -1338,8 +1429,9 @@ error_info( [] -> []; [Event|_] -> [Event] end] ++ - [FmtData,Class,FixedReason, - CallbackMode] ++ + [FmtData, + Class,FixedReason, + CBMode] ++ case Q of [_|[_|_] = Events] -> [Events]; _ -> [] diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index e092940174..eef8f265c4 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -37,7 +37,7 @@ all() -> {group, stop_handle_event}, {group, abnormal}, {group, abnormal_handle_event}, - shutdown, stop_and_reply, event_order, code_change, + shutdown, stop_and_reply, enter_events, event_order, code_change, {group, sys}, hibernate, enter_loop]. @@ -556,7 +556,8 @@ stop_and_reply(_Config) -> {stop_and_reply,Reason, [R1,{reply,From2,Reply2}]} end}, - {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []), + {ok,STM} = + gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []), Self = self(), Tag1 = make_ref(), @@ -581,6 +582,61 @@ stop_and_reply(_Config) -> +enter_events(_Config) -> + process_flag(trap_exit, true), + Self = self(), + + Machine = + %% Abusing the internal format of From... + #{init => + fun () -> + {ok,start,1} + end, + start => + fun (enter, Prev, N) -> + Self ! {enter,start,Prev,N}, + {keep_state,N + 1}; + (internal, Prev, N) -> + Self ! {internal,start,Prev,N}, + {keep_state,N + 1}; + ({call,From}, echo, N) -> + {next_state,wait,N + 1,{reply,From,{echo,start,N}}}; + ({call,From}, {stop,Reason}, N) -> + {stop_and_reply,Reason,[{reply,From,{stop,N}}],N + 1} + end, + wait => + fun (enter, Prev, N) -> + Self ! {enter,wait,Prev,N}, + {keep_state,N + 1}; + ({call,From}, echo, N) -> + {next_state,start,N + 1, + [{next_event,internal,wait}, + {reply,From,{echo,wait,N}}]} + end}, + {ok,STM} = + gen_statem:start_link( + ?MODULE, {map_statem,Machine,[state_entry_events]}, []), + + [{enter,start,start,1}] = flush(), + {echo,start,2} = gen_statem:call(STM, echo), + [{enter,wait,start,3}] = flush(), + {wait,[4|_]} = sys:get_state(STM), + {echo,wait,4} = gen_statem:call(STM, echo), + [{enter,start,wait,5},{internal,start,wait,6}] = flush(), + {stop,7} = gen_statem:call(STM, {stop,bye}), + [{'EXIT',STM,bye}] = flush(), + + {noproc,_} = + ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), + case flush() of + [] -> + ok; + Other2 -> + ct:fail({unexpected,Other2}) + end. + + + event_order(_Config) -> process_flag(trap_exit, true), @@ -623,7 +679,7 @@ event_order(_Config) -> Result end}, - {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []), + {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []), Self = self(), Tag1 = make_ref(), gen_statem:cast(STM, {reply,{Self,Tag1},ok1}), @@ -1315,9 +1371,9 @@ init({callback_mode,CallbackMode,Arg}) -> ets:new(?MODULE, [named_table,private]), ets:insert(?MODULE, {callback_mode,CallbackMode}), init(Arg); -init({map_statem,#{init := Init}=Machine}) -> +init({map_statem,#{init := Init}=Machine,Modes}) -> ets:new(?MODULE, [named_table,private]), - ets:insert(?MODULE, {callback_mode,handle_event_function}), + ets:insert(?MODULE, {callback_mode,[handle_event_function|Modes]}), case Init() of {ok,State,Data,Ops} -> {ok,State,[Data|Machine],Ops}; diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 57e47431b8..8090016b54 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -109,7 +109,7 @@ State(S) x Event(E) -> Actions(A), State(S')

 StateName(EventType, EventContent, Data) ->
-    .. code for actions here ...
+    ... code for actions here ...
     {next_state, NewStateName, NewData}.
@@ -120,7 +120,7 @@ StateName(EventType, EventContent, Data) ->

 handle_event(EventType, EventContent, State, Data) ->
-    .. code for actions here ...
+    ... code for actions here ...
     {next_state, NewState, NewData}
@@ -192,13 +192,42 @@ handle_event(EventType, EventContent, State, Data) -> + + +
+ + State Entry Events +

+ The gen_statem behavior can regardless of callback mode + automatically generate an + + event whenever the state changes + + so you can write state entry code + near the rest of the state transition rules. + It typically looks like this: +

+
+StateName(enter, _OldState, Data) ->
+    ... code for state entry here ...
+    {keep_state, NewData};
+StateName(EventType, EventContent, Data) ->
+    ... code for actions here ...
+    {next_state, NewStateName, NewData}.
+

+ Depending on how your state machine is specified, + this can be a very useful feature, but if you use it + you will have to handle the state entry events in all states. +

+
+
Example

This example starts off as equivalent to the example in section - gen_fsm Behavior. + gen_fsm-Behavior. In later sections, additions and tweaks are made using features in gen_statem that gen_fsm does not have. The end of this chapter provides the example again @@ -722,6 +751,12 @@ stop() -> Generated by any regular process message sent to the gen_statem process. + enter + + Generated by a state transition with + OldState =/= NewState when running with + state entry events. + timeout Generated by state transition action @@ -981,7 +1016,7 @@ init(Code) -> callback_mode() -> state_functions. -locked(internal, enter, _Data) -> +locked(internal, enter, Data) -> do_lock(), {keep_state,Data#{remaining => Code}}; locked( @@ -992,7 +1027,7 @@ locked( enter(next_state, open, Data); ... -open(internal, enter, _Data) -> +open(internal, enter, Data) -> Tref = erlang:start_timer(10000, self(), lock), do_unlock(), {keep_state,Data#{timer => Tref}}; @@ -1005,6 +1040,49 @@ enter(Tag, State, Data) -> ]]>

+ + +
+ Using State Entry Events +

+ Here is the same example as the previous but instead using + the built in + state entry events. + You will have to handle the state entry events in every state. + If you want state entry code in just a few states the previous + example may be more suitable. +

+ + process_flag(trap_exit, true), + Data = #{code => Code}, + {ok, locked, Data}. + +callback_mode() -> + [state_functions,state_entry_events]. + +locked(enter, _OldState, Data) -> + do_lock(), + {keep_state,Data#{remaining => Code}}; +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> + {next_state, open, Data}; +... + +open(enter, _OldState, Data) -> + Tref = erlang:start_timer(10000, self(), lock), + do_unlock(), + {keep_state,Data#{timer => Tref}}; +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> + {next_state, locked, Data}; +... + ]]> +
+
@@ -1054,12 +1132,12 @@ code_length() -> init(Code) -> process_flag(trap_exit, true), Data = #{code => Code}, - enter(ok, locked, Data). + {ok, locked, Data}. callback_mode() -> - state_functions. + [state_functions,state_entry_events]. -locked(internal, enter, #{code := Code} = Data) -> +locked(enter, _OldState, #{code := Code} = Data) -> do_lock(), {keep_state,Data#{remaining => Code}}; locked( @@ -1067,7 +1145,7 @@ locked( #{code := Code, remaining := Remaining} = Data) -> case Remaining of [Digit] -> % Complete - enter(next_state, open, Data); + {next_state, open, Data}; [Digit|Rest] -> % Incomplete {keep_state,Data#{remaining := Rest}}; [_|_] -> % Wrong @@ -1076,12 +1154,12 @@ locked( locked(EventType, EventContent, Data) -> handle_event(EventType, EventContent, Data). -open(internal, enter, Data) -> +open(enter, _OldState, Data) -> Tref = erlang:start_timer(10000, self(), lock), do_unlock(), {keep_state,Data#{timer => Tref}}; open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> - enter(next_state, locked, Data); + {next_state, locked, Data}; open(cast, {button,_}, _) -> {keep_state_and_data,[postpone]}; open(EventType, EventContent, Data) -> @@ -1089,8 +1167,6 @@ open(EventType, EventContent, Data) -> handle_event({call,From}, code_length, #{code := Code}) -> {keep_state_and_data,[{reply,From,length(Code)}]}. -enter(Tag, State, Data) -> - {Tag,State,Data,[{next_event,internal,enter}]}. do_lock() -> io:format("Locked~n", []). @@ -1111,8 +1187,9 @@ code_change(_Vsn, State, Data, _Extra) -> This section describes what to change in the example to use one handle_event/4 function. The previously used approach to first branch depending on event - does not work that well here because of the generated - entry actions, so this example first branches depending on state: + does not work that well here because of + the state entry events, + so this example first branches depending on state:

... callback_mode() -> - handle_event_function. + [handle_event_function,state_entry_events]. %% State: locked -handle_event(internal, enter, locked, #{code := Code} = Data) -> +handle_event(enter, _OldState, locked, #{code := Code} = Data) -> do_lock(), {keep_state,Data#{remaining => Code}}; handle_event( @@ -1131,7 +1208,7 @@ handle_event( #{code := Code, remaining := Remaining} = Data) -> case Remaining of [Digit] -> % Complete - enter(next_state, open, Data); + {next_state, open, Data}; [Digit|Rest] -> % Incomplete {keep_state,Data#{remaining := Rest}}; [_|_] -> % Wrong @@ -1139,12 +1216,12 @@ handle_event( end; %% %% State: open -handle_event(internal, enter, open, Data) -> +handle_event(enter, _OldState, open, Data) -> Tref = erlang:start_timer(10000, self(), lock), do_unlock(), {keep_state,Data#{timer => Tref}}; handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) -> - enter(next_state, locked, Data); + {next_state, locked, Data}; handle_event(cast, {button,_}, open, _) -> {keep_state_and_data,[postpone]}; %% @@ -1305,10 +1382,10 @@ set_lock_button(LockButton) -> init({Code,LockButton}) -> process_flag(trap_exit, true), Data = #{code => Code, remaining => undefined, timer => undefined}, - enter(ok, {locked,LockButton}, Data, []). + {ok, {locked,LockButton}, Data}. callback_mode() -> - handle_event_function. + [handle_event_function,state_entry_events]. handle_event( {call,From}, {set_lock_button,NewLockButton}, @@ -1320,55 +1397,52 @@ handle_event( {_StateName,_LockButton}, #{code := Code}) -> {keep_state_and_data, [{reply,From,length(Code)}]}; +%% +%% State: locked handle_event( EventType, EventContent, {locked,LockButton}, #{code := Code, remaining := Remaining} = Data) -> case {EventType,EventContent} of - {internal,enter} -> + {enter,_OldState} -> do_lock(), {keep_state,Data#{remaining := Code}}; {{call,From},{button,Digit}} -> case Remaining of [Digit] -> % Complete - next_state( - {open,LockButton}, Data, - [{reply,From,ok}]); + {next_state, {open,LockButton}, Data, + [{reply,From,ok}]}; [Digit|Rest] -> % Incomplete - {keep_state,Data#{remaining := Rest}, + {keep_state, Data#{remaining := Rest}, [{reply,From,ok}]}; [_|_] -> % Wrong - {keep_state,Data#{remaining := Code}, + {keep_state, Data#{remaining := Code}, [{reply,From,ok}]} end end; +%% +%% State: open handle_event( EventType, EventContent, {open,LockButton}, #{timer := Timer} = Data) -> case {EventType,EventContent} of - {internal,enter} -> + {enter,_OldState} -> Tref = erlang:start_timer(10000, self(), lock), do_unlock(), {keep_state,Data#{timer := Tref}}; {info,{timeout,Timer,lock}} -> - next_state({locked,LockButton}, Data, []); + {next_state, {locked,LockButton}, Data}; {{call,From},{button,Digit}} -> if Digit =:= LockButton -> erlang:cancel_timer(Timer), - next_state( - {locked,LockButton}, Data, - [{reply,From,locked}]); + {next_state, {locked,LockButton}, Data, + [{reply,From,locked}]); true -> {keep_state_and_data, [postpone]} end end. -next_state(State, Data, Actions) -> - enter(next_state, State, Data, Actions). -enter(Tag, State, Data, Actions) -> - {Tag,State,Data,[{next_event,internal,enter}|Actions]}. - do_lock() -> io:format("Locked~n", []). do_unlock() -> @@ -1434,7 +1508,7 @@ handle_event( EventType, EventContent, {open,LockButton}, #{timer := Timer} = Data) -> case {EventType,EventContent} of - {internal,enter} -> + {enter,_OldState} -> Tref = erlang:start_timer(10000, self(), lock), do_unlock(), {keep_state,Data#{timer := Tref},[hibernate]}; -- cgit v1.2.3