Skip to content

Instantly share code, notes, and snippets.

@RaimoNiskanen
Last active January 1, 2023 19:03
Show Gist options
  • Save RaimoNiskanen/0f42a5256131bb440e284cd17eef33cf to your computer and use it in GitHub Desktop.
Save RaimoNiskanen/0f42a5256131bb440e284cd17eef33cf to your computer and use it in GitHub Desktop.
gen_statem Code Lock Example in Erlang for SF CodeBeam 2018
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock).
-export([down/1,up/1,code_length/0]). % API
-export([start_link/1,stop/0]). % Server
-export([init/1,callback_mode/0,terminate/3]). % Behaviour
-export([locked/3,open/3]). % States
%% Start and stop
start_link(Code) ->
gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
stop() ->
gen_statem:stop(?NAME).
callback_mode() -> [state_functions,state_enter].
%% API
down(Button) ->
gen_statem:cast(?NAME, {down,Button}).
up(Button) ->
gen_statem:cast(?NAME, {up,Button}).
code_length() ->
gen_statem:call(?NAME, code_length).
%% Init and terminate
init(Code) ->
process_flag(trap_exit, true),
Data = #{code => Code, length => length(Code)},
{ok, locked, Data}.
terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
%% Common events
-define(HANDLE_COMMON,
?FUNCTION_NAME(T, C, D) -> handle_common((T), (C), (D))).
handle_common({call,From}, code_length, #{length := Length}) ->
{keep_state_and_data,
[{reply,From,Length}]};
%%
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,Button}}]};
#{} ->
keep_state_and_data
end.
%% States
locked(enter, _OldState, Data) ->
do_lock(),
{keep_state, Data#{buttons => []}};
locked(state_timeout, button, Data) ->
{keep_state, Data#{buttons := []}};
%%
locked(
internal, {button,Button},
#{code := Code, length := Length, buttons := Buttons} = Data) ->
NewButtons =
if
length(Buttons) < Length ->
Buttons;
true ->
tl(Buttons)
end ++ [Button],
if
NewButtons =:= Code -> % Correct
{next_state, open, Data};
true -> % Incomplete | Incorrect
{keep_state, Data#{buttons := NewButtons},
[{state_timeout,30000,button}]}
end;
?HANDLE_COMMON.
open(enter, _OldState, _Data) ->
do_unlock(),
{keep_state_and_data,
[{state_timeout,10000,locked}]};
open(state_timeout, NextState, Data) ->
{next_state, NextState, Data};
%%
open(internal, {button,_}, Data) ->
{next_state, ?FUNCTION_NAME, Data,
[postpone]};
?HANDLE_COMMON.
% Helpers
do_lock() ->
io:format("~n======= Locked~n", []).
do_unlock() ->
io:format("~n======= Open~n", []).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment