Skip to content

Commit

Permalink
add input to state to support native store of keypresses
Browse files Browse the repository at this point in the history
  • Loading branch information
jjcarstens committed May 12, 2019
1 parent 404b7aa commit a69264e
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 11 deletions.
15 changes: 12 additions & 3 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ defmodule MyModule do
use Keypad, row_pins: [5,6,7], col_pins: [22,23,24,25], size: "3x4"

@impl true
def handle_keypress(key), do: IO.inspect(key, label: "KEYPRESS: ")
def handle_keypress(key, state) do
IO.inspect(key, label: "KEYPRESS: ")
state
end
end
```

Expand All @@ -48,7 +51,10 @@ defmodule MyModule do
]

@impl true
def handle_keypress(key), do: IO.inspect(key, label: "KEYPRESS: ")
def handle_keypress(key, state) do
IO.inspect(key, label: "KEYPRESS: ")
state
end
end
```

Expand All @@ -63,6 +69,9 @@ defmodule MyModule do
]

@impl true
def handle_keypress(key), do: IO.inspect(key, label: "KEYPRESS: ")
def handle_keypress(key, state) do
IO.inspect(key, label: "KEYPRESS: ")
state
end
end
```
52 changes: 47 additions & 5 deletions lib/keypad.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,47 @@ defmodule Keypad do
* `:col_pins` - List of integers which map to corresponding GPIO to as `OUTPUT` pins for keypad columns
* defaults to `[5, 6, 13, 26]`
"""
@callback handle_keypress(key :: any) :: any

@doc """
Required callback to handle keypress events based on defined matrix values.
It's first argument will be the result of the keypress according to the defined matrix (most typically a
binary string, though you can use anything you'd like). The second argument is the state of the keypad
GenServer. You are required to return the state in this function.
There is an optional field in the state called `:input` which is initialized as an empty string `""`. You can
use this to keep input events from keypresses and build them as needed, such as putting multiple keypresses
together to determine a password. **Note**: You will be responsible for resetting this input as needed.
This is not required and you can optionally use other measures to keep rolling state, such as `Agent`.
```elixir
defmodule MyKeypad do
use Keypad
require Logger
@impl true
def handle_keypress(key, %{input: ""} = state) do
Logger.info("First Keypress: \#{key}")
Process.send_after(self(), :reset, 5000) # Reset input after 5 seconds
%{state | input: key}
end
@impl true
def handle_keypress(key, %{input: input} = state) do
Logger.info("Keypress: \#{key}")
%{state | input: input <> key}
end
@impl true
def handle_info(:reset, state) do
{:noreply, %{state | input: ""}}
end
end
```
"""
@callback handle_keypress(key :: any, map) :: map

defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
Expand All @@ -36,7 +76,7 @@ defmodule Keypad do
alias __MODULE__

defmodule State do
defstruct row_pins: [17, 27, 23, 24], col_pins: [5, 6, 13, 26], matrix: nil, size: nil, last_message_at: 0
defstruct row_pins: [17, 27, 23, 24], col_pins: [5, 6, 13, 26], input: "", matrix: nil, size: nil, last_message_at: 0
end

defguard valid_press(current, prev) when ((current - prev)/1.0e6) > 100
Expand Down Expand Up @@ -65,7 +105,7 @@ defmodule Keypad do
def handle_info({:circuits_gpio, pin_num, time, 0}, %{last_message_at: prev} = state) when valid_press(time, prev) do
{row_pin, row_index} = Stream.with_index(state.row_pins)
|> Enum.find(fn {row, _i} -> Circuits.GPIO.pin(row) == pin_num end)
state.col_pins
val = state.col_pins
|> Stream.with_index()
|> Enum.reduce_while([], fn {col, col_index}, acc ->
# Write the column pin HIGH then read the row pin again.
Expand All @@ -79,13 +119,15 @@ defmodule Keypad do
# We can use the row and column indexes as x,y of the matrix
# to get which specific key character the press belongs to.
val = Enum.at(state.matrix, row_index) |> Enum.at(col_index)
apply(__MODULE__, :handle_keypress, [val])
{:halt, []}

{:halt, val}
0 ->
{:cont, []}
end
end)

state = apply(__MODULE__, :handle_keypress, [val, state])

{:noreply, %{state | last_message_at: time}}
end

Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Keypad.MixProject do
def project do
[
app: :keypad,
version: "0.1.0",
version: "0.2.0",
elixir: "~> 1.8",
name: "Keypad",
description: "A small library to interact with keypads connected to GPIO pins",
Expand Down Expand Up @@ -35,7 +35,7 @@ defmodule Keypad.MixProject do

defp deps do
[
{:circuits_gpio, "~> 0.3.0"},
{:circuits_gpio, "~> 0.3"},
{:ex_doc, "~> 0.19", only: :dev, runtime: false}
]
end
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%{
"circuits_gpio": {:hex, :circuits_gpio, "0.3.1", "bfbc01bba0de0d820ee5cc60593d809095f10922f65de8bde26795790d830250", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"circuits_gpio": {:hex, :circuits_gpio, "0.4.1", "344dd34f2517687fd28723e5552571babff5469db05b697181cab860fe7eff23", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.5.2", "96a28c79f5b8d34879cd95ebc04d2a0d678cfbbd3e74c43cb63a76adf0ee8054", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
Expand Down

0 comments on commit a69264e

Please sign in to comment.