From 2699cd204f8cc2b3a4f457ff6d25651508db42b3 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 27 Mar 2018 10:22:07 +0200 Subject: Improve doc, change images to .svg --- system/doc/design_principles/Makefile | 12 +- system/doc/design_principles/code_lock.dia | Bin 2945 -> 2605 bytes system/doc/design_principles/code_lock.png | Bin 59827 -> 0 bytes system/doc/design_principles/code_lock.svg | 132 +++++++ system/doc/design_principles/code_lock_2.dia | Bin 2956 -> 2854 bytes system/doc/design_principles/code_lock_2.png | Bin 55553 -> 0 bytes system/doc/design_principles/code_lock_2.svg | 140 +++++++ system/doc/design_principles/statem.xml | 550 ++++++++++++++++----------- 8 files changed, 610 insertions(+), 224 deletions(-) delete mode 100644 system/doc/design_principles/code_lock.png create mode 100644 system/doc/design_principles/code_lock.svg delete mode 100644 system/doc/design_principles/code_lock_2.png create mode 100644 system/doc/design_principles/code_lock_2.svg diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile index 5743a50b47..1f570f5271 100644 --- a/system/doc/design_principles/Makefile +++ b/system/doc/design_principles/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1997-2016. All Rights Reserved. +# Copyright Ericsson AB 1997-2018. 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. @@ -57,11 +57,11 @@ GIF_FILES = \ sup5.gif \ sup6.gif -PNG_FILES = \ - code_lock.png \ - code_lock_2.png +SVG_FILES = \ + code_lock.svg \ + code_lock_2.svg -IMAGE_FILES = $(GIF_FILES) $(PNG_FILES) +IMAGE_FILES = $(GIF_FILES) $(SVG_FILES) XML_FILES = \ $(BOOK_FILES) $(XML_CHAPTER_FILES) \ @@ -90,7 +90,7 @@ _create_dirs := $(shell mkdir -p $(HTMLDIR)) $(HTMLDIR)/%.gif: %.gif $(INSTALL_DATA) $< $@ -$(HTMLDIR)/%.png: %.png +$(HTMLDIR)/%.svg: %.svg $(INSTALL_DATA) $< $@ docs: html diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia index eaa2aca5b0..fe43d6da2c 100644 Binary files a/system/doc/design_principles/code_lock.dia and b/system/doc/design_principles/code_lock.dia differ diff --git a/system/doc/design_principles/code_lock.png b/system/doc/design_principles/code_lock.png deleted file mode 100644 index 40bd35fc74..0000000000 Binary files a/system/doc/design_principles/code_lock.png and /dev/null differ diff --git a/system/doc/design_principles/code_lock.svg b/system/doc/design_principles/code_lock.svg new file mode 100644 index 0000000000..223e121486 --- /dev/null +++ b/system/doc/design_principles/code_lock.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + locked + + + + + + + open + + + + + + + {button,Button} + + + + + + + Correct Code? + + + + + + + do_lock() + + + + + + + state_timeout + + + + + + + init + + + + + + + + + + + + + + + Y + + + N + + + + + + + + + + + {button,Digit} + + + + + + + + + + + + do_lock() + Clear Buttons + + + + + + + do_unlock() + Clear Buttons + state_timeout 10 s + + + + + + + + + + + + + + + + + + Collect Buttons + + + + + + + + + diff --git a/system/doc/design_principles/code_lock_2.dia b/system/doc/design_principles/code_lock_2.dia index 3b9ba554d8..31eb0fb6eb 100644 Binary files a/system/doc/design_principles/code_lock_2.dia and b/system/doc/design_principles/code_lock_2.dia differ diff --git a/system/doc/design_principles/code_lock_2.png b/system/doc/design_principles/code_lock_2.png deleted file mode 100644 index 3aca9dd5aa..0000000000 Binary files a/system/doc/design_principles/code_lock_2.png and /dev/null differ diff --git a/system/doc/design_principles/code_lock_2.svg b/system/doc/design_principles/code_lock_2.svg new file mode 100644 index 0000000000..d3e15e7577 --- /dev/null +++ b/system/doc/design_principles/code_lock_2.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + locked + + + + + + + open + + + + + + + {button,Button} + + + + + + + do_unlock() + state_timeout 10 s + + + + + + + do_lock() + Clear Buttons + + + + + + + state_timeout + + + + + + + init + + + + + + + + + + + + + + + + + + + + + + + + + + + state_timeout 30 s + + + + + + + state_timeout + + + + + + + Collect Buttons + + + + + + + + + + + + + Clear Buttons + + + + + + + + + + Correct Code? + + + + N + + + Y + + + + + + + + diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 16f6ce8348..5269d23487 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -356,8 +356,8 @@ State(S) x Event(E) -> Actions(A), State(S')

- {stop_and_reply, Reason, NewData, Actions}
- {stop_and_reply, Reason, Actions} + {stop_and_reply, Reason, NewData, ReplyActions}
+ {stop_and_reply, Reason, ReplyActions}

@@ -377,7 +377,7 @@ State(S) x Event(E) -> Actions(A), State(S') callback function is called before any event handler - is called. This function behaves exactly as an event handler + is called. This function behaves like an event handler function, but gets its only argument Args from the gen_statem @@ -678,13 +678,14 @@ StateName(EventType, EventContent, Data) -> A door with a code lock can be seen as a state machine. Initially, the door is locked. When someone presses a button, an event is generated. - Depending on what buttons have been pressed before, - the sequence so far can be correct, incomplete, or wrong. + The pressed buttons are collected, up to the number of buttons + in the correct code. If correct, the door is unlocked for 10 seconds (10,000 milliseconds). - If incomplete, we wait for another button to be pressed. If - wrong, we start all over, waiting for a new button sequence. + If not correct, we wait for a new button to be pressed.

- + + Code Lock State Diagram

@@ -698,37 +699,44 @@ StateName(EventType, EventContent, Data) -> -export([start_link/1]). -export([button/1]). --export([init/1,callback_mode/0,terminate/3,code_change/4]). +-export([init/1,callback_mode/0,terminate/3]). -export([locked/3,open/3]). start_link(Code) -> gen_statem:start_link({local,?NAME}, ?MODULE, Code, []). -button(Digit) -> - gen_statem:cast(?NAME, {button,Digit}). +button(Button) -> + gen_statem:cast(?NAME, {button,Button}). init(Code) -> do_lock(), - Data = #{code => Code, remaining => Code}, + Data = #{code => Code, length => length(Code), buttons => []}, {ok, locked, Data}. callback_mode() -> state_functions. - + ]]> + - case Remaining of - [Digit] -> + cast, {button,Button}, + #{code := Code, length := Length, buttons := Buttons} = Data) -> + NewButtons = + if + length(Buttons) < Length -> + Buttons; + true -> + tl(Buttons) + end ++ [Button], + if + NewButtons =:= Code -> % Correct do_unlock(), - {next_state, open, Data#{remaining := Code}, + {next_state, open, Data#{buttons := []}, [{state_timeout,10000,lock}]}; - [Digit|Rest] -> % Incomplete - {next_state, locked, Data#{remaining := Rest}}; - _Wrong -> - {next_state, locked, Data#{remaining := Code}} + true -> % Incomplete | Incorrect + {next_state, locked, Data#{buttons := NewButtons}} end. - + ]]> + do_lock(), {next_state, locked, Data}; @@ -743,8 +751,6 @@ do_unlock() -> 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.

@@ -820,17 +826,17 @@ start_link(Code) -> in this case locked; assuming that 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 key code that stores - the correct button sequence, and key remaining - that stores the remaining correct button sequence - (the same as the code to begin with). + with key code that stores the correct button sequence, + key length store its length, + and key buttons that stores the collected buttons + up to the same length.

do_lock(), - Data = #{code => Code, remaining => Code}, - {ok,locked,Data}. + Data = #{code => Code, length => length(Code), buttons => []}, + {ok, locked, Data}. ]]>

Function gen_statem:start_link @@ -848,10 +854,6 @@ init(Code) -> a gen_statem that is not part of a supervision tree.

- - state_functions. - ]]>

Function Module:callback_mode/0 @@ -859,8 +861,12 @@ callback_mode() -> CallbackMode for the callback module, in this case state_functions. - That is, each state has got its own handler function. + That is, each state has got its own handler function:

+ + state_functions. + ]]> @@ -884,7 +890,7 @@ button(Digit) -> {button,Digit} is the event content.

- The event is made into a message and sent to the gen_statem. + The event is 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}, @@ -893,44 +899,48 @@ button(Digit) -> NewStateName is the name of the next state to go to. NewData is a new value for the server data of the gen_statem, and Actions is a list of - actions on the gen_statem engine. + actions to be performed by the gen_statem engine.

+ - case Remaining of - [Digit] -> % Complete + cast, {button,Button}, + #{code := Code, length := Length, buttons := Buttons} = Data) -> + NewButtons = + if + length(Buttons) < Length -> + Buttons; + true -> + tl(Buttons) + end ++ [Button], + if + NewButtons =:= Code -> % Correct do_unlock(), - {next_state, open, Data#{remaining := Code}, + {next_state, open, Data#{buttons := []}, [{state_timeout,10000,lock}]}; - [Digit|Rest] -> % Incomplete - {next_state, locked, Data#{remaining := Rest}}; - [_|_] -> % Wrong - {next_state, locked, Data#{remaining := Code}} + true -> % Incomplete | Incorrect + {next_state, locked, Data#{buttons := NewButtons}} end. - -open(state_timeout, lock, Data) -> - do_lock(), - {next_state, locked, Data}; -open(cast, {button,_}, Data) -> - {next_state, open, Data}. ]]>

- If the door is locked and a button is pressed, the pressed - button is compared with the next correct button. + In state locked, when a button is pressed, + it is collected with the last pressed buttons + up to the length of the correct dode, + and compared with the correct code. 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. -

-

- If the whole code is correct, the server changes states - to open. + When changing to state open, the collected + buttons are reset, the lock unlocked, and a state timer + for 10 s is started.

+ + + {next_state, open, Data}. + ]]>

In state open, a button event is ignored by staying in the same state. This can also be done @@ -948,7 +958,7 @@ open(cast, {button,_}, Data) -> the following tuple is returned from locked/2:

@@ -986,9 +996,9 @@ open(state_timeout, lock, Data) ->

Consider a code_length/0 function that returns the length of the correct code - (that should not be sensitive to reveal). + (that should not be too sensitive to reveal). We dispatch all events that are not state-specific - to the common function handle_event/3: + to the common function handle_common/3:

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

+ Another way to do it is through a convenience macro + ?HANDLE_COMMON/3: +

+ + gen_statem:call(?NAME, code_length). + +-define(HANDLE_COMMON(T, C, D), + ?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)}]}. + +... +locked(...) -> ... ; +?HANDLE_COMMON. + +... +open(...) -> ... ; +?HANDLE_COMMON. + ]]> +

This example uses gen_statem:call/2, @@ -1047,16 +1085,22 @@ callback_mode() -> 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}, + #{length := Length, buttons := Buttons} = Data, + NewButtons = + if + length(Buttons) < Length -> + Buttons; + true -> + tl(Buttons) + end ++ [Button], + if + NewButtons =:= Code -> % Correct + do_unlock(), + {next_state, open, Data#{buttons := []}, [{state_timeout,10000,lock}]}; - [Digit|Rest] -> % Incomplete - {keep_state, Data#{remaining := Rest}}; - [_|_] -> % Wrong - {keep_state, Data#{remaining := Code}} - end; + true -> % Incomplete | Incorrect + {keep_state, Data#{buttons := NewButtons}} + end; open -> keep_state_and_data end; @@ -1165,16 +1209,15 @@ stop() -> - {next_state, locked, Data#{remaining := Code}}; +locked(timeout, _, Data) -> + {next_state, locked, Data#{buttons := []}}; locked( cast, {button,Digit}, - #{code := Code, remaining := Remaining} = Data) -> + #{code := Code, length := Length, buttons := Buttons} = Data) -> ... - [Digit|Rest] -> % Incomplete - {next_state, locked, Data#{remaining := Rest}, 30000}; + true -> % Incomplete | Incorrect + {next_state, locked, Data#{buttons := NewButtons}, + 30000} ... ]]>

@@ -1189,6 +1232,13 @@ locked( Whatever event you act on has already cancelled the event time-out...

+

+ Note that an event time-out does not work well with + when you have for example a status call as in + All State Events, + or handle unknown events, since all kinds of events + will cancel the event time-out. +

@@ -1222,12 +1272,13 @@ locked( ... locked( cast, {button,Digit}, - #{code := Code, remaining := Remaining} = Data) -> - case Remaining of - [Digit] -> + #{code := Code, length := Length, buttons := Buttons} = Data) -> +... + if + NewButtons =:= Code -> % Correct do_unlock(), - {next_state, open, Data#{remaining := Code}, - [{{timeout,open_tm},10000,lock}]}; + {next_state, open, Data#{buttons := []}, + [{{timeout,open_tm},10000,lock}]}; ... open({timeout,open_tm}, lock, Data) -> @@ -1273,12 +1324,13 @@ open(cast, {button,_}, Data) -> ... locked( cast, {button,Digit}, - #{code := Code, remaining := Remaining} = Data) -> - case Remaining of - [Digit] -> + #{code := Code, length := Length, buttons := Buttons} = Data) -> +... + if + NewButtons =:= Code -> % Correct do_unlock(), Tref = erlang:start_timer(10000, self(), lock), - {next_state, open, Data#{remaining := Code, timer => Tref}}; + {next_state, open, Data#{buttons := [], timer => Tref}}; ... open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> @@ -1398,28 +1450,38 @@ start_link(Code) -> fun () -> true = register(?NAME, self()), do_lock(), - locked(Code, Code) + locked(Code, length(Code), []) end). -button(Digit) -> - ?NAME ! {button,Digit}. - -locked(Code, [Digit|Remaining]) -> +button(Button) -> + ?NAME ! {button,Button}. + ]]> + receive - {button,Digit} when Remaining =:= [] -> - do_unlock(), - open(Code); - {button,Digit} -> - locked(Code, Remaining); - {button,_} -> - locked(Code, Code) + {button,Button} -> + NewButtons = + if + length(Buttons) < Length -> + Buttons; + true -> + tl(Buttons) + end ++ [Button], + if + NewButtons =:= Code -> % Correct + do_unlock(), + open(Code, Length); + true -> % Incomplete | Incorrect + locked(Code, Length, NewButtons) + end end. - -open(Code) -> + ]]> + receive after 10000 -> do_lock(), - locked(Code, Code) + locked(Code, Length, []) end. do_lock() -> @@ -1483,7 +1545,7 @@ do_unlock() -> ... init(Code) -> process_flag(trap_exit, true), - Data = #{code => Code}, + Data = #{code => Code, length = length(Code)}, {ok, locked, Data}. callback_mode() -> @@ -1491,13 +1553,14 @@ callback_mode() -> locked(enter, _OldState, Data) -> do_lock(), - {keep_state,Data#{remaining => Code}}; + {keep_state,Data#{buttons => []}}; locked( cast, {button,Digit}, - #{code := Code, remaining := Remaining} = Data) -> - case Remaining of - [Digit] -> - {next_state, open, Data}; + #{code := Code, length := Length, buttons := Buttons} = Data) -> +... + if + NewButtons =:= Code -> % Correct + {next_state, open, Data}; ... open(enter, _OldState, _Data) -> @@ -1557,48 +1620,50 @@ open(state_timeout, lock, Data) -> to synchronize the state machines.

- The following example uses an input model where you give the lock - characters with put_chars(Chars) and then call - enter() to finish the input. + The following example uses an input model where the buttons + generate up/down events and the lock responds to an up + event after the corresponding down event.

- gen_statem:call(?NAME, {chars,Chars}). +down(button) -> + gen_statem:cast(?NAME, {down,Button}). -enter() -> - gen_statem:call(?NAME, enter). +up(button) -> + gen_statem:cast(?NAME, {up,Button}). ... locked(enter, _OldState, Data) -> do_lock(), {keep_state,Data#{remaining => Code, buf => []}}; +locked( + internal, {button,Digit}, + #{code := Code, length := Length, buttons := Buttons} = Data) -> ... - -handle_event({call,From}, {chars,Chars}, #{buf := Buf} = Data) -> - {keep_state, Data#{buf := [Chars|Buf], - [{reply,From,ok}]}; -handle_event({call,From}, enter, #{buf := Buf} = Data) -> - Chars = unicode:characters_to_binary(lists:reverse(Buf)), - try binary_to_integer(Chars) of - Digit -> - {keep_state, Data#{buf := []}, - [{reply,From,ok}, - {next_event,internal,{button,Chars}}]} - catch - error:badarg -> - {keep_state, Data#{buf := []}, - [{reply,From,{error,not_an_integer}}]} + ]]> + + {keep_state, Data#{button := Button}; +handle_common(cast, {up,Button}, Data) -> + case Data of + #{button := Button} -> + {keep_state,maps:remove(button, Data), + [{next_event,internal,{button,Button}}]}; + #{} -> + keep_state_and_data end; ... + +open(internal, {button,_}, Data) -> + {keep_state,Data,[postpone]}; +... ]]>

If you start this program with code_lock:start([17]) - you can unlock with code_lock:put_chars(<<"001">>), - code_lock:put_chars(<<"7">>), code_lock:enter(). + you can unlock with code_lock:down(17), code_lock:up(17).

@@ -1612,13 +1677,15 @@ handle_event({call,From}, enter, #{buf := Buf} = Data) -> modifications and some more using state enter calls, which deserves a new state diagram:

- + + Code Lock State Diagram Revisited

Notice that this state diagram does not specify how to handle a button event in the state open. So, you need to - read somewhere else that unspecified events + read here that unspecified events must be ignored as in not consumed but handled in some other state. Also, the state diagram does not show that the code_length/0 call must be handled in every state. @@ -1636,8 +1703,8 @@ handle_event({call,From}, enter, #{buf := Buf} = Data) -> -define(NAME, code_lock_2). -export([start_link/1,stop/0]). --export([button/1,code_length/0]). --export([init/1,callback_mode/0,terminate/3,code_change/4]). +-export([down/1,up/1,code_length/0]). +-export([init/1,callback_mode/0,terminate/3]). -export([locked/3,open/3]). start_link(Code) -> @@ -1645,52 +1712,74 @@ start_link(Code) -> stop() -> gen_statem:stop(?NAME). -button(Digit) -> - gen_statem:cast(?NAME, {button,Digit}). +down(Digit) -> + gen_statem:cast(?NAME, {down,Digit}). +up(Digit) -> + gen_statem:cast(?NAME, {up,Digit}). code_length() -> gen_statem:call(?NAME, code_length). - + ]]> + process_flag(trap_exit, true), - Data = #{code => Code}, + Data = #{code => Code, length => Length, buttons => []}, {ok, locked, Data}. callback_mode() -> [state_functions,state_enter]. -locked(enter, _OldState, #{code := Code} = Data) -> +-define(HANDLE_COMMON, + ?FUNCTION_NAME(T, C, D) -> handle_common((T), (C), (D))). +%% +handle_common(cast, {down,Button}, Data) -> + {keep_state, Data#{button => Button}}; +handle_common(cast, {up,Button}, Data) -> + case Data of + #{button := Button} -> + {keep_state, maps:remove(button, Data), + [{next_event,internal,{button,Data}}]}; + #{} -> + keep_state_and_data + end; +handle_common({call,From}, code_length, #{code := Code}) -> + {keep_state_and_data, [{reply,From,length(Code)}]}. + ]]> + do_lock(), - {keep_state, Data#{remaining => Code}}; -locked( - timeout, _, - #{code := Code, remaining := Remaining} = Data) -> - {keep_state, Data#{remaining := Code}}; + {keep_state, Data#{buttons := []}}; +locked(state_timeout, button, Data) -> + {keep_state, Data#{buttons := []}}; locked( - cast, {button,Digit}, - #{code := Code, remaining := Remaining} = Data) -> - case Remaining of - [Digit] -> % Complete - {next_state, open, Data}; - [Digit|Rest] -> % Incomplete - {keep_state, Data#{remaining := Rest}, 30000}; - [_|_] -> % Wrong - {keep_state, Data#{remaining := Code}} + internal, {button,Digit}, + #{code := Code, length := Length, buttons := Buttons} = Data) -> + NewButtons = + if + length(Buttons) < Length -> + Buttons; + true -> + tl(Buttons) + end ++ [Button], + if + NewButtons =:= Code -> % Correct + do_unlock(), + {next_state, open, Data, + [{state_timeout,10000,lock}]}; + true -> % Incomplete | Incorrect + {keep_state, Data#{buttons := NewButtons}, + [{state_timeout,30000,button}]} end; -locked(EventType, EventContent, Data) -> - handle_event(EventType, EventContent, Data). - +?HANDLE_COMMON. + ]]> + do_unlock(), {keep_state_and_data, [{state_timeout,10000,lock}]}; open(state_timeout, lock, Data) -> {next_state, locked, Data}; -open(cast, {button,_}, _) -> +open(internal, {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)}]}. +?HANDLE_COMMON. do_lock() -> io:format("Locked~n", []). @@ -1700,8 +1789,6 @@ do_unlock() -> terminate(_Reason, State, _Data) -> State =/= locked andalso do_lock(), ok. -code_change(_Vsn, State, Data, _Extra) -> - {ok,State,Data}. ]]> @@ -1724,26 +1811,32 @@ callback_mode() -> [handle_event_function,state_enter]. %% State: locked -handle_event( - enter, _OldState, locked, - #{code := Code} = Data) -> +handle_event(enter, _OldState, locked, Data) -> do_lock(), - {keep_state, Data#{remaining => Code}}; + {keep_state, Data#{buttons := []}}; +handle_event(state_timeout, button, locked, Data) -> + {keep_state, Data#{buttons := []}}; handle_event( - timeout, _, locked, - #{code := Code, remaining := Remaining} = Data) -> - {keep_state, Data#{remaining := Code}}; -handle_event( - cast, {button,Digit}, locked, - #{code := Code, remaining := Remaining} = Data) -> - case Remaining of - [Digit] -> % Complete - {next_state, open, Data}; - [Digit|Rest] -> % Incomplete - {keep_state, Data#{remaining := Rest}, 30000}; - [_|_] -> % Wrong - {keep_state, Data#{remaining := Code}} + internal, {button,Digit}, locked, + #{code := Code, length := Length, buttons := Buttons} = Data) -> + NewButtons = + if + length(Buttons) < Length -> + Buttons; + true -> + tl(Buttons) + end ++ [Button], + if + NewButtons =:= Code -> % Correct + do_unlock(), + {next_state, open, Data, + [{state_timeout,10000,lock}]}; + true -> % Incomplete | Incorrect + {keep_state, Data#{buttons := NewButtons}, + [{state_timeout,30000,button}]} end; + ]]> + @@ -1753,10 +1846,22 @@ handle_event(state_timeout, lock, open, Data) -> {next_state, locked, Data}; handle_event(cast, {button,_}, open, _) -> {keep_state_and_data,[postpone]}; + ]]> + - {keep_state_and_data, [{reply,From,length(Code)}]}. +handle_event(cast, {down,Button}, _State, Data) -> + {keep_state, Data#{button => Button}}; +handle_event(cast, {up,Button}, _State, Data) -> + case Data of + #{button := Button} -> + {keep_state, maps:remove(button, Data), + [{state_timeout,30000,button}]}; + #{} -> + keep_state_and_data + end; +handle_event({call,From}, code_length, _State, #{length := Length}) -> + {keep_state_and_data, [{reply,From,Length}]}. ... ]]> @@ -1800,7 +1905,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->

@@ -1808,7 +1913,6 @@ format_status(Opt, [_PDict,State,Data]) -> {State, maps:filter( fun (code, _) -> false; - (remaining, _) -> false; (_, _) -> true end, Data)}, @@ -1896,7 +2000,7 @@ format_status(Opt, [_PDict,State,Data]) -> -export([start_link/2,stop/0]). -export([button/1,code_length/0,set_lock_button/1]). --export([init/1,callback_mode/0,terminate/3,code_change/4,format_status/2]). +-export([init/1,callback_mode/0,terminate/3,format_status/2]). -export([handle_event/4]). start_link(Code, LockButton) -> @@ -1911,10 +2015,11 @@ code_length() -> gen_statem:call(?NAME, code_length). set_lock_button(LockButton) -> gen_statem:call(?NAME, {set_lock_button,LockButton}). - + ]]> + process_flag(trap_exit, true), - Data = #{code => Code, remaining => undefined}, + Data = #{code => Code, length => length(Code), buttons => []}, {ok, {locked,LockButton}, Data}. callback_mode() -> @@ -1927,33 +2032,41 @@ handle_event( [{reply,From,OldLockButton}]}; handle_event( {call,From}, code_length, - {_StateName,_LockButton}, #{code := Code}) -> + {_StateName,_LockButton}, #{length := Length}) -> {keep_state_and_data, - [{reply,From,length(Code)}]}; + [{reply,From,Length}]}; + ]]> + +handle_event(EventType, EventContent, {locked,LockButton}, Data) -> case {EventType, EventContent} of {enter, _OldState} -> do_lock(), - {keep_state, Data#{remaining := Code}}; - {timeout, _} -> - {keep_state, Data#{remaining := Code}}; + {keep_state, Data#{buttons := []}}; + {state_timeout, button} -> + {keep_state, Data#{buttons := []}}; {{call,From}, {button,Digit}} -> - case Remaining of - [Digit] -> % Complete + #{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}]}; - [Digit|Rest] -> % Incomplete - {keep_state, Data#{remaining := Rest}, - [{reply,From,ok}, 30000]}; - [_|_] -> % Wrong - {keep_state, Data#{remaining := Code}, - [{reply,From,ok}]} - end + #{} -> + {keep_state, Data#{buttons := NewButtons}, + [{reply,From,ok}, + {state_timeout,30000,button}]} + end end; + ]]> + do_unlock(), - {keep_state_and_data, [{state_timeout,10000,lock}]}; + {keep_state_and_data, + [{state_timeout,10000,lock}]}; {state_timeout, lock} -> {next_state, {locked,LockButton}, Data}; {{call,From}, {button,Digit}} -> @@ -1975,7 +2089,8 @@ handle_event( [postpone]} end end. - + ]]> + io:format("Locked~n", []). do_unlock() -> @@ -1984,8 +2099,6 @@ do_unlock() -> terminate(_Reason, State, _Data) -> State =/= locked andalso do_lock(), ok. -code_change(_Vsn, State, Data, _Extra) -> - {ok,State,Data}. format_status(Opt, [_PDict,State,Data]) -> StateData = {State, @@ -2046,7 +2159,8 @@ handle_event( {enter, _OldState} -> do_unlock(), {keep_state_and_data, - [{state_timeout,10000,lock},hibernate]}; + [{state_timeout,10000,lock}, + hibernate]}; ... ]]>

-- cgit v1.2.3