Skip to content

Instantly share code, notes, and snippets.

@RaimoNiskanen
Last active February 9, 2019 21:27
Show Gist options
  • Save RaimoNiskanen/6c423d6bf3b053b47bea41468323c336 to your computer and use it in GitHub Desktop.
Save RaimoNiskanen/6c423d6bf3b053b47bea41468323c336 to your computer and use it in GitHub Desktop.
gen_statem Code Lock Example in Elixir for SF CodeBeam 2018
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