Skip to content

Instantly share code, notes, and snippets.

@peerreynders
Created January 3, 2019 21:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save peerreynders/009acfacf67e4b6289d982f4e800ac23 to your computer and use it in GitHub Desktop.
Save peerreynders/009acfacf67e4b6289d982f4e800ac23 to your computer and use it in GitHub Desktop.
Erlang gen_statem OTP design principles "handle_event_function" callback mode revised example "translated" to Elixir
# file: code_lock.ex
# Translated from: http://erlang.org/doc/design_principles/statem.html#example-revisited
# callback mode: :handle_event_function
#
defmodule CodeLock do
@behaviour :gen_statem
@name :code_lock_2
def start_link(code),
do: :gen_statem.start_link({:local, @name}, __MODULE__, code, [])
def down(button),
do: :gen_statem.cast(@name, {:down, button})
def up(button),
do: :gen_statem.cast(@name, {:up, button})
def code_length(),
do: :gen_statem.call(@name, :code_length)
## Mandatory callback functions
def stop(),
do: :gen_statem.stop(@name)
def terminate(_reason, state, _data),
do: if(state !== :locked, do: do_lock(), else: :ok)
def code_change(_vsn, state, data, _extra),
do: {:ok, state, data}
# returns {:ok, state, data}
def init(code) do
Process.flag(:trap_exit, true)
{:ok, :locked, %{code: code, length: length(code)}}
end
def callback_mode(),
do: [:handle_event_function, :state_enter]
defp clear_buttons(data),
do: Map.put(data, :buttons, [])
## State: :locked
##
def handle_event(:enter, _state, :locked, data) do
do_lock()
{:keep_state, clear_buttons(data)}
end
def handle_event(:state_timeout, :button, :locked, data) do
{:keep_state, clear_buttons(data)}
end
def handle_event(
:internal,
{:button, button},
:locked,
%{code: code, length: length, buttons: buttons} = data
) do
new_buttons = if(length(buttons) < length, do: buttons, else: tl(buttons)) ++ [button]
if new_buttons === code do
# correct
{:next_state, :open, data}
else
# Incomplete / Incorrect
# Time in milliseconds
{:keep_state, %{data | buttons: new_buttons}, [{:state_timeout, 30000, :button}]}
end
end
## State: :open
##
def handle_event(:enter, _state, :open, _data) do
do_unlock()
{:keep_state_and_data, [{:state_timeout, 10000, :lock}]}
end
def handle_event(:state_timeout, :lock, :open, data) do
{:next_state, :locked, data}
end
def handle_event(:internal, {:button, _}, :open, _) do
{:keep_state_and_data, [:postpone]}
end
## Common events
def handle_event(:cast, {:down, button}, _state, data) do
{:keep_state, Map.put(data, :button, button)}
end
def handle_event(:cast, {:up, button}, _state, data) do
case data do
%{button: ^button} ->
{:keep_state, Map.delete(data, :button), [{:next_event, :internal, {:button, button}}]}
_ ->
:keep_state_and_data
end
end
def handle_event({:call, from}, :code_length, _state, %{length: length}) do
{:keep_state_and_data, [{:reply, from, length}]}
end
defp do_lock(),
do: IO.puts("Lock")
defp do_unlock(),
do: IO.puts("Unlock")
end
$ iex
Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c("code_lock.ex")
[CodeLock]
iex(2)> CodeLock.start_link([:a,:b,:c])
Lock
{:ok, #PID<0.109.0>}
iex(3)> CodeLock.down(:a)
:ok
iex(4)> CodeLock.up(:a)
:ok
iex(5)> CodeLock.down(:b)
:ok
iex(6)> CodeLock.up(:b)
:ok
iex(7)> CodeLock.down(:c)
:ok
iex(8)> CodeLock.up(:c)
Unlock
:ok

# wait 10+ seconds
Lock    
iex(9)> CodeLock.down(:a)
:ok
iex(10)> CodeLock.up(:a)
:ok
iex(11)> CodeLock.down(:b)
:ok
iex(12)> CodeLock.up(:b)
:ok

# wait 30+ seconds
iex(13)> CodeLock.down(:c)
:ok
iex(14)> CodeLock.up(:c)
:ok

# doesn't unlock
iex(15)> CodeLock.down(:a)
:ok
iex(16)> CodeLock.up(:a)
:ok
iex(17)> CodeLock.down(:b)
:ok
iex(18)> CodeLock.up(:b)
:ok
iex(19)> CodeLock.down(:c)
:ok
iex(20)> CodeLock.up(:c)
Unlock
:ok
iex(21)> CodeLock.stop()
Lock
:ok
iex(22)> 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment