Last active
February 9, 2019 21:27
-
-
Save RaimoNiskanen/6c423d6bf3b053b47bea41468323c336 to your computer and use it in GitHub Desktop.
gen_statem Code Lock Example in Elixir for SF CodeBeam 2018
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule CodeLock do | |
@name :code_lock | |
## Start and stop | |
def start_link(code) do | |
:gen_statem.start_link({:local,@name}, __MODULE__, code, []) | |
end | |
def stop, do: :gen_statem.stop @name | |
def callback_mode, do: [:state_functions,:state_enter] | |
## API | |
def down(button) do | |
:gen_statem.cast @name, {:down,button} | |
end | |
def up(button), do: :gen_statem.cast @name, {:up,button} | |
def code_length, do: :gen_statem.call @name, :code_length | |
## Init and terminate | |
def init(code) do | |
Process.flag :trap_exit, true | |
data = %{code: code, length: Kernel.length(code)} | |
{:ok, :locked, data} | |
end | |
def terminate(_reason, state, _data) do | |
state !== :locked && do_lock() | |
:ok | |
end | |
## Common events | |
defp handle_common({:call,from}, :code_length, %{length: length}) do | |
{:keep_state_and_data, | |
[{:reply,from,length}]} | |
end | |
## | |
defp handle_common(:cast, {:down,button}, data) do | |
{:keep_state, Map.put(data, :button, button)} | |
end | |
defp handle_common(:cast, {:up,button}, data) do | |
case data do | |
%{button: ^button} -> | |
{:keep_state, Map.delete(data, :button), | |
[{:next_event,:internal,{:button,button}}]} | |
%{} -> | |
:keep_state_and_data | |
end | |
end | |
## States | |
def locked(:enter, _, data) do | |
do_lock() | |
{:keep_state, Map.put(data, :buttons, [])} | |
end | |
def locked(:state_timeout, :button, data) do | |
{:keep_state, %{data | buttons: []}} | |
end | |
def locked( | |
:internal, {:button,button}, | |
%{code: code, length: length, buttons: buttons} = data) do | |
new_buttons = | |
if Kernel.length(buttons) < length do | |
buttons | |
else | |
Kernel.tl(buttons) | |
end ++ [button] | |
if new_buttons === code do | |
{:next_state, :open, data} | |
else | |
{:keep_state, #{data | buttons: new_buttons}, | |
{:state_timeout,30000,:button}} | |
end | |
end | |
def locked(t, c, d), do: handle_common t, c, d | |
def open(:enter, _, _) do | |
do_unlock() | |
{:keep_state_and_data, | |
[{:state_timeout,10000,:locked}]} | |
end | |
def open(:state_timeout, next_state, data) do | |
{:next_state, next_state, data} | |
end | |
def open(:internal, {:button,_}, data) do | |
{:next_state, :open, data, | |
[:postpone]} | |
end | |
def open(t, c, d), do: handle_common t, c, d | |
## Helpers | |
defp do_lock, do: IO.puts "\n======= Locked" | |
defp do_unlock, do: IO.puts "\n======= Open" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment