From 7563c1929cd9bfceced790d0784210ea287d536e Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Wed, 6 Apr 2016 17:13:08 +0200 Subject: Write Design Principles chapter --- system/doc/design_principles/Makefile | 4 +- system/doc/design_principles/code_lock.dia | Bin 0 -> 2968 bytes system/doc/design_principles/code_lock.gif | Bin 0 -> 35031 bytes system/doc/design_principles/code_lock_2.dia | Bin 0 -> 2646 bytes system/doc/design_principles/code_lock_2.gif | Bin 0 -> 29420 bytes system/doc/design_principles/part.xml | 3 +- system/doc/design_principles/statem.xml | 1082 ++++++++++++++++++++++++++ system/doc/design_principles/xmlfiles.mk | 3 +- 8 files changed, 1089 insertions(+), 3 deletions(-) create mode 100644 system/doc/design_principles/code_lock.dia create mode 100644 system/doc/design_principles/code_lock.gif create mode 100644 system/doc/design_principles/code_lock_2.dia create mode 100644 system/doc/design_principles/code_lock_2.gif create mode 100644 system/doc/design_principles/statem.xml (limited to 'system/doc') diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile index 29df484279..2ccf477e83 100644 --- a/system/doc/design_principles/Makefile +++ b/system/doc/design_principles/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1997-2012. All Rights Reserved. +# Copyright Ericsson AB 1997-2016. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -48,6 +48,8 @@ BOOK_FILES = book.xml GIF_FILES = \ note.gif \ clientserver.gif \ + code_lock.gif \ + code_lock_2.gif \ dist1.gif \ dist2.gif \ dist3.gif \ diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia new file mode 100644 index 0000000000..d8262138a3 Binary files /dev/null and b/system/doc/design_principles/code_lock.dia differ diff --git a/system/doc/design_principles/code_lock.gif b/system/doc/design_principles/code_lock.gif new file mode 100644 index 0000000000..adcfee7eff Binary files /dev/null and b/system/doc/design_principles/code_lock.gif differ diff --git a/system/doc/design_principles/code_lock_2.dia b/system/doc/design_principles/code_lock_2.dia new file mode 100644 index 0000000000..4e82a9e1d6 Binary files /dev/null and b/system/doc/design_principles/code_lock_2.dia differ diff --git a/system/doc/design_principles/code_lock_2.gif b/system/doc/design_principles/code_lock_2.gif new file mode 100644 index 0000000000..150515c3b6 Binary files /dev/null and b/system/doc/design_principles/code_lock_2.gif differ diff --git a/system/doc/design_principles/part.xml b/system/doc/design_principles/part.xml index 42fd3beb6a..6495211e04 100644 --- a/system/doc/design_principles/part.xml +++ b/system/doc/design_principles/part.xml @@ -4,7 +4,7 @@
- 19972013 + 19972016 Ericsson AB. All Rights Reserved. @@ -31,6 +31,7 @@ + diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml new file mode 100644 index 0000000000..c10afe482d --- /dev/null +++ b/system/doc/design_principles/statem.xml @@ -0,0 +1,1082 @@ + + + + +
+ + 2016 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + gen_statem Behaviour + + + + + statem.xml +
+ +

+ This section is to be read with the + gen_statem(3) + manual page in STDLIB, where all interface functions and callback + functions are described in detail. +

+ + + +
+ Event Driven State Machines +

+ Established Automata theory does not deal much with + how a state transition is triggered, + but in general assumes that the output is a function + of the input (and the state) and that they are + some kind of values. +

+

+ For an Event Driven State Machine the input is an event + that triggers a state transition and the output + is actions executed during the state transition. + It can analogously to the mathematical model of a + Finite State Machine be described as + a set of relations of the form: +

+
+State(S) x Event(E) -> Actions(A), State(S')
+

These relations are interpreted as meaning:

+

+ If we are in state S and event E occurs, we + are to perform actions A and make a transition to + state S'. +

+

+ Note that S' may be equal to S. +

+

+ Since A and S' depend only on + S and E the kind of state machine described + here is a Mealy Machine. +

+

+ Like most gen_ behaviours, gen_statem keeps + a server Data besides the state. This and the fact that + there is no restriction on the number of states + (assuming enough virtual machine memory) + or on the number of distinct input events actually makes + a state machine implemented with this behaviour Turing complete. + But it feels mostly like an Event Driven Mealy Machine. +

+

+ The gen_statem behaviour supports two different + callback modes. In the mode state_functions, + the state transition rules are written as a number of Erlang + functions, which conform to the following convention: +

+
+StateName(EventType, EventContent, Data) ->
+    .. code for actions here ...
+    {next_state, StateName', Data'}.
+

+ In the mode handle_event_function there is only one + Erlang function that implements all state transition rules: +

+
+handle_event(EventType, EventContent, State, Data) ->
+    .. code for actions here ...
+    {next_state, State', Data'}
+

+ Both these modes allow other return tuples + that you can find in the + + reference manual. + + These other return tuples can for example stop the machine, + execute state transition actions on the machine engine itself + and send replies. +

+
+ + + +
+ Example +

+ This is an example starting off as equivalent to the the example in the + gen_fsm behaviour + description. In later chapters additions and tweaks are made + using features in gen_statem that gen_fsm does not have. + At the end of this section you can find the example again + with all the added features. +

+

+ A door with a code lock can be viewed as a state machine. + Initially, the door is locked. Anytime someone presses a button, + this generates an event. + Depending on what buttons have been pressed before, + the sequence so far can be correct, incomplete, or wrong. +

+

+ If it is correct, the door is unlocked for 10 seconds (10000 ms). + If it is incomplete, we wait for another button to be pressed. If + it is is wrong, we start all over, + waiting for a new button sequence. +

+ + Code lock state diagram + +

+ We can implement such a code lock state machine using + gen_statem with the following callback module: +

+ + + gen_statem:start_link({local,code_lock}, ?MODULE, Code, []). + +button(Digit) -> + gen_statem:cast(code_lock, {button,Digit}). + + +init(Code) -> + do_lock(), + Data = #{code => Code, remaining => Code}, + {state_functions,locked,Data}. + +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> + do_unlock(), + {next_state,open,Data#{remaining := Code},10000}; + [Digit|Rest] -> % Incomplete + {next_state,locked,Data#{remaining := Rest}}; + _Wrong -> + {next_state,locked,Data#{remaining := Code}} + end. + +open(timeout, _, Data) -> + do_lock(), + {next_state,locked,Data}; +open(cast, {button,_}, Data) -> + do_lock(), + {next_state,locked,Data}. + +do_lock() -> + io:format("Lock~n", []). +do_unlock() -> + io:format("Unlock~n", []). + +terminate(_Reason, State, _Data) -> + State =/= locked andalso do_lock(), + ok. +code_change(_Vsn, State, Data, _Extra) -> + {ok,State,Data}. + ]]> +

The code is explained in the next sections.

+
+ + + +
+ Starting gen_statem +

+ In the example in the previous section, the gen_statem is + started by calling code_lock:start_link(Code): +

+ + gen_statem:start_link({local,code_lock}, ?MODULE, Code, []). + ]]> +

+ start_link calls the function + + gen_statem:start_link/4 + + which spawns and links to a new process; a gen_statem. +

+ + +

+ The first argument, {local,code_lock}, specifies + the name. In this case, the gen_statem is locally + registered as code_lock. +

+

+ If the name is omitted, the gen_statem is not registered. + Instead its pid must be used. The name can also be given + as {global,Name}, in which case the gen_statem is + registered using + + global:register_name/2. + +

+
+ +

+ The second argument, ?MODULE, is the name of + the callback module, that is; the module where the callback + functions are located, which is this module. +

+

+ The interface functions (start_link/1 and button/1) + are located in the same module as the callback functions + (init/1, locked/3, and open/3). + It is normally good programming practice to have the client + side and the server side code contained in one module. +

+
+ +

+ The third argument, Code, is a list of digits that + is the correct unlock code which is passsed + to the callback function init/1. +

+
+ +

+ The fourth argument, [], is a list of options. See the + + gen_statem:start_link/3 + + manual page for available options. +

+
+
+

+ If name registration succeeds, the new gen_statem process + calls the callback function code_lock:init(Code). + This function is expected to return {CallbackMode,State,Data}, + where CallbackMode selects callback module state function + mode, in this case state_functions that is each state + has got its own handler function. + State is the initial state of the gen_statem, + in this case locked; assuming the door is locked to begin with. + Data is the internal server data of the gen_statem. + Here the server data is a + + map + + with the key code that stores + the correct button sequence and the key remaining + that stores the remaining correct button sequence + (the same as the code to begin with). +

+ + do_lock(), + Data = #{code => Code, remaining => Code}, + {state_functions,locked,Data}. + ]]> +

+ + gen_statem:start_link + + is synchronous. It does not return until the gen_statem + has been initialized and is ready to receive events. +

+

+ + gen_statem:start_link + + must be used if the gen_statem + is part of a supervision tree, that is; started by a supervisor. + There is another function; + + gen_statem:start + + to start a standalone gen_statem, that is; + a gen_statem that is not part of a supervision tree. +

+
+ + + +
+ Events and Handling them +

The function notifying the code lock about a button event is + implemented using + + gen_statem:cast/2: + +

+ + gen_statem:cast(code_lock, {button,Digit}). + ]]> +

+ code_lock is the name of the gen_statem and must + agree with the name used to start it. + {button,Digit} is the actual event content. +

+

+ The event is made into a message and sent to the gen_statem. + When the event is received, the gen_statem calls + StateName(cast, Event, Data), which is expected to + return a tuple {next_state,NewStateName,NewData}. + StateName is the name of the current state and + NewStateName is the name of the next state to go to. + NewData is a new value for the server data of + the gen_statem. +

+ + case Remaining of + [Digit] -> % Complete + do_unlock(), + {next_state,open,Data#{remaining := Code},10000}; + [Digit|Rest] -> % Incomplete + {next_state,locked,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {next_state,locked,Data#{remaining := Code}} + end. + +open(timeout, _, Data) -> + do_lock(), + {next_state,locked,Data}; +open(cast, {button,_}, Data) -> + do_lock(), + {next_state,locked,Data}. + ]]> +

+ If the door is locked and a button is pressed, the pressed + button is compared with the next correct button and, + depending on the result, the door is either unlocked + and the gen_statem goes to state open, + or the door remains in state locked. +

+

+ If the pressed button is incorrect the server data + restarts from the start of the code sequence. +

+

+ In state open any button locks the door since + any event cancels the event timer so we will not get + a timeout event after a button event. +

+
+ +
+ Event Time-Outs +

+ When a correct code has been given, the door is unlocked and + the following tuple is returned from locked/2: +

+ +

+ 10000 is a time-out value in milliseconds. + After this time, that is; 10 seconds, a time-out occurs. + Then, StateName(timeout, 10000, Data) is called. + The time-out occurs when the door has been in state open + for 10 seconds. After that the door is locked again: +

+ + do_lock(), + {next_state,locked,Data}; + ]]> +
+ + + +
+ All State Events +

+ Sometimes an event can arrive in any state of the gen_statem. + It is convenient to handle these in a common state handler function + that all state functions call for events not specific to the state. +

+

+ Let's introduce a code_length/0 function that returns + the length of the correct code + (that should not be sensitive to reveal...). + We'll dispatch all events that are not state specific + to the common function handle_event/3. +

+ + gen_statem:call(code_lock, code_length). + +... +locked(...) -> ... ; +locked(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +... +open(...) -> ... ; +open(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +handle_event({call,From}, code_length, #{code := Code} = Data) -> + {keep_state,Data,[{reply,From,length(Code)}]}. + ]]> +

+ This example uses + + gen_statem:call/2 + + which waits for a reply from the server. + The reply is sent with a {reply,From,Reply} tuple + in an action list in the {keep_state,...} tuple + that retains the current state. +

+
+ + + +
+ One Event Handler +

+ If you use the mode handle_event_function + all events are handled in handle_event/4 and we + may (but do not have to) use an event-centered approach + where we dispatch on event first and then state: +

+ + Data = #{code => Code, remaining => Code}, + {handle_event_function,locked,Data}. + +handle_event(cast, {button,Digit}, State, #{code := Code} = Data) -> + case State of + locked -> + case maps:get(remaining, Data) of + [Digit] -> % Complete + do_unlock(), + {next_state,open,Data#{remaining := Code},10000}; + [Digit|Rest] -> % Incomplete + {keep_state,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}} + end; + open -> + do_lock(), + {next_state,locked,Data} + end; +handle_event(timeout, _, open, Data) -> + do_lock(), + {next_state,locked,Data}. + +... + ]]> +
+ + + +
+ Stopping + +
+ In a Supervision Tree +

+ If the gen_statem is part of a supervision tree, + no stop function is needed. + The gen_statem is automatically terminated by its supervisor. + Exactly how this is done is defined by a + shutdown strategy + set in the supervisor. +

+

+ If it is necessary to clean up before termination, the shutdown + strategy must be a time-out value and the gen_statem must + in the init/1 function set itself to trap exit signals + by calling + + process_flag(trap_exit, true). + + When ordered to shutdown, the gen_statem then calls + the callback function + terminate(shutdown, State, Data): +

+ + process_flag(trap_exit, true), + ... + {CallbackMode,State,Data}. + ]]> +

+ In this example we let the terminate/3 function + lock the door if it is open so we do not accidentally leave the door + open when the supervision tree terminates. +

+ + State =/= locked andalso do_lock(), + ok. + ]]> +
+ +
+ Standalone gen_statem +

+ If the gen_statem is not part of a supervision tree, + it can be stopped using + + gen_statem:stop, + + preferably through an API function: +

+ + gen_statem:stop(code_lock). + ]]> +

+ This makes the gen_statem call the terminate/3 + callback function just like for a supervised server + and waits for the process to terminate. +

+
+
+ + + +
+ Actions +

+ In the first chapters we mentioned actions as a part of + the general state machine model, and these actions + are implemented with the code the gen_statem + callback module executes in an event handling + callback function before returning + to the gen_statem engine. +

+

+ There are more specific state transition actions + that a callback function can order the gen_statem + engine to do after the callback function return. + These are ordered by returning a list of + + actions + + in the + + return tuple + + from the + + callback function. + + These state transition actions affect the gen_statem + engine itself. They can: +

+ + Postpone the current event. + Hibernate the gen_statem. + Start an event timeout. + Reply to a caller. + Generate the next event to handle. + +

+ We have mentioned the event timeout + and replying to a caller in the example above. + An example of event postponing comes in later in this chapter. + See the + + documentation + + for details. You can for example actually reply to several callers + and generate multiple next events to handle. +

+
+ + + +
+ Event Types +

+ So far we have mentioned a few + + event types. + + Events of all types are handled in the same callback function, + for a given state, and the function gets + EventType and EventContent as arguments. +

+

+ Here is the complete list of event types and where + they come from: +

+ + cast + + Generated by + + gen_statem:cast. + + + {call,From} + + Generated by + + gen_statem:call + + where From is the reply address to use + when replying either through the state transition action + {reply,From,Msg} or by calling + + gen_statem:reply. + + + info + + Generated by any regular process message sent to + the gen_statem process. + + timeout + + Generated by the state transition action + {timeout,Time,EventContent} (or its short form Time) + timer timing out. + + internal + + Generated by the state transition action + {next_event,internal,EventContent}. + In fact all event types above can be generated using + {next_event,EventType,EventContent}. + + +
+ + + +
+ State Timeouts +

+ The timeout event generated by the state transition action + {timeout,Time,EventContent} is an event timeout, + that is; if an event arrives the timer is cancelled. + You get either an event or a timeout but not both. +

+

+ Often you want a timer to not be cancelled by any event + or you want to start a timer in one state and respond + to the timeout in another. This can be accomplished + with a regular erlang timer: + + erlang:start_timer. + +

+

+ Looking at the example in this chapter so far; using the + gen_statem event timer has the consequence that + if a button event is generated while in the open state, + the timeout is cancelled and the button event is delivered. + Therefore we chose to lock the door if this happended. +

+

+ Suppose we do not want a button to lock the door, + instead we want to ignore button events in the open state. + Then we start a timer when entering the open state + and wait for it to expire while ignoring button events: +

+ + case Remaining of + [Digit] -> + do_unlock(), + Tref = erlang:start_timer(10000, self(), lock), + {next_state,open,Data#{remaining := Code, timer := Tref}}; +... + +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> + do_lock(), + {next_state,locked,Data}; +open(cast, {button,_}, Data) -> + {keep_state,Data}; +... + ]]> +

+ If you need to cancel a timer due to some other event you can use + + erlang:cancel_timer(Tref). + + Note that a timeout message can not arrive after this, + unless you have postponed it before (why on earth one would do that). +

+

+ Another way to cancel a timer is to not cancel it, + but instead to ignore it if it arrives in a state + where it is known to be late. +

+
+ + + +
+ Postponing Events +

+ If you want to ignore a particular event in the current state + and handle it in a future state, you can postpone the event. + A postponed event is retried after the state has + changed i.e OldState =/= NewState. +

+

+ Postponing is ordered by the + + state transition action + + postpone. +

+

+ In this example, instead of ignoring button events + while in the open state we can postpone them + and they will be queued and later handled in the locked state: +

+ + {keep_state,Data,[postpone]}; +... + ]]> +
+ Fuzzy State Diagrams +

+ It is not uncommon that a state diagram does not specify + how to handle events that are not illustrated + in a particular state in the diagram. + Hopefully this is described in an associated text + or from the context. +

+

+ Possible actions may be; ignore as in drop the event + (maybe log it) or deal with the event in some other state + as in postpone it. +

+
+
+ Selective Receive +

+ Erlang's selective receive statement is often used to + describe simple state machine examples in straightforward + Erlang code. Here is a possible implementation of + the first example: +

+ + spawn( + fun () -> + true = register(code_lock, self()), + do_lock(), + locked(Code, Code) + end). + +button(Digit) -> + code_lock ! {button,Digit}. + +locked(Code, [Digit|Remaining]) -> + receive + {button,Digit} when Remaining =:= [] -> + do_unlock(), + open(Code); + {button,Digit} -> + locked(Code, Remaining); + {button,_} -> + locked(Code, Code) + end. + +open(Code) -> + receive + after 10000 -> + do_lock(), + locked(Code, Code) + end. + +do_lock() -> + io:format("Locked~n", []). +do_unlock() -> + io:format("Open~n", []). + ]]> +

+ The selective receive in this case causes open + to implicitly postpone any events to the locked state. +

+

+ The + + state transition action + + postpone is designed to be able to model + selective receive. Selective receive implicitly postpones + any not received events, but the postpone + state transition action explicitly postpones a received event. +

+

+ Other than that both mechanisms have got the same theoretical + time and memory complexity, while the selective receive + language construct has got smaller constant factors. +

+
+
+ + + +
+ Self Generated Events +

+ It may be beneficial in some cases to be able to generate events + to your own state machine. This can be done with the + + state transition action + + {next_event,EventType,EventContent}. +

+

+ You can generate events of any existing + + type, + + but the internal type can only be generated through the + next_event action and hence can not come from an external source, + so you can be certain that an internal event is an event + from your state machine to itself. +

+

+ One example of using self generated events may be when you have + a state machine specification that uses state entry actions. + That you could code using a dedicated function + to do the state transition. But if you want that code to be + visible besides the other state logic you can insert + an internal event that does the entry actions. + This has the same unfortunate consequence as using + state transition functions that everywhere you go to + the state in question you will have to explicitly + insert the internal event + or use state transition function. +

+

+ Here is an implementation of entry actions + using internal events with content enter + utilizing a helper function enter/3 for state entry: +

+ + Data = #{code => Code}, + enter(state_functions, locked, Data). + +... +locked(internal, enter, _Data) -> + do_lock(), + {keep_state,Data#{remaining => Code}}; +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> + enter(next_state, open, Data); +... + +open(internal, enter, _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); +... + +enter(Tag, State, Data) -> + {Tag,State,Data,[{next_event,internal,enter}]}. + ]]> +
+ + + +
+ Example Revisited +

+ Here is the example after all mentioned modifications + and some more utilizing the entry actions, + which deserves a new state diagram: +

+ + Code lock state diagram revisited + +

+ Note that this state diagram does not specify how to handle + a button event in the state open, so you will have to + read some other place that is here that unspecified events + shall be ignored as in not consumed but handled in some other state. + Nor does it show that the code_length/0 call shall be + handled in every state. +

+
+ Callback Mode: state_functions +

+ Using state functions: +

+ + gen_statem:start_link({local,code_lock}, ?MODULE, Code, []). +stop() -> + gen_statem:stop(code_lock). + +button(Digit) -> + gen_statem:cast(code_lock, {button,Digit}). +code_length() -> + gen_statem:call(code_lock, code_length). + +init(Code) -> + Data = #{code => Code}, + enter(state_functions, locked, Data). + +locked(internal, enter, #{code := Code} = Data) -> + do_lock(), + {keep_state,Data#{remaining => Code}}; +locked( + cast, {button,Digit}, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> % Complete + enter(next_state, open, Data); + [Digit|Rest] -> % Incomplete + {keep_state,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}} + end; +locked(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +open(internal, enter, 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); +open(cast, {button,_}, _) -> + {keep_state_and_data,[postpone]}; +open(EventType, EventContent, Data) -> + handle_event(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", []). +do_unlock() -> + io:format("Open~n", []). + +terminate(_Reason, State, _Data) -> + State =/= locked andalso do_lock(), + ok. +code_change(_Vsn, State, Data, _Extra) -> + {ok,State,Data}. + ]]> +
+
+ Callback Mode: handle_event_function +

+ What to change to use one handle_event/4 function. + Here a clean first-dispatch-on-event approach + does not work that well due to the generated + entry actions: +

+ + process_flag(trap_exit, true), + Data = #{code => Code}, + enter(handle_event_function, locked, Data). + +... + +%% State: locked +handle_event(internal, enter, locked, #{code := Code} = Data) -> + do_lock(), + {keep_state,Data#{remaining => Code}}; +handle_event( + cast, {button,Digit}, locked, + #{code := Code, remaining := Remaining} = Data) -> + case Remaining of + [Digit] -> % Complete + enter(next_state, open, Data, []); + [Digit|Rest] -> % Incomplete + {keep_state,Data#{remaining := Rest}}; + [_|_] -> % Wrong + {keep_state,Data#{remaining := Code}} + end; +%% +%% State: open +handle_event(internal, enter, 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, []); +handle_event(cast, {button,_}, open, _) -> + {keep_state_and_data,[postpone]}; +%% +%% Any state +handle_event({call,From}, code_length, _State, #{code := Code}) -> + {keep_state_and_data,[{reply,From,length(Code)}]}. + +... + ]]> +
+

+ Note that postponing buttons from the locked state + to the open state feels like the wrong thing to do + for a code lock, but it illustrates event postponing. +

+
+ +
diff --git a/system/doc/design_principles/xmlfiles.mk b/system/doc/design_principles/xmlfiles.mk index 3032ac3ab9..e476255d62 100644 --- a/system/doc/design_principles/xmlfiles.mk +++ b/system/doc/design_principles/xmlfiles.mk @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2009. All Rights Reserved. +# Copyright Ericsson AB 2009-2016. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ DESIGN_PRINCIPLES_CHAPTER_FILES = \ distributed_applications.xml \ events.xml \ fsm.xml \ + statem.xml \ gen_server_concepts.xml \ included_applications.xml \ release_handling.xml \ -- cgit v1.2.3