Skip to content

Commit

Permalink
Merge pull request #46 from axelclark/ultrasonic
Browse files Browse the repository at this point in the history
Update Ultrasonic to use Poller
  • Loading branch information
adkron committed May 27, 2019
2 parents 3a94766 + b85c16c commit 7080639
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 29 deletions.
65 changes: 39 additions & 26 deletions lib/grovepi/ultrasonic.ex
Original file line number Diff line number Diff line change
@@ -1,50 +1,63 @@
defmodule GrovePi.Ultrasonic do
alias GrovePi.Board

use GrovePi.Poller,
default_trigger: GrovePi.Ultrasonic.DefaultTrigger,
read_type: GrovePi.Digital.level()

@moduledoc """
Read distance from the Grove Ultrasonic sensor.
Read distance and subscribe to changes from the Grove Ultrasonic Ranger sensor.
Listen for events from a GrovePi Ultrasonic Ranger
sensor. This module is configured for the Ultrasonic Ranger, the one that comes
with the GrovePi+ Starter Kit. There is only one type of event by default;
`:changed`. When registering for an event the Ultrasonic will send a message in the
form of `{pin, :changed, %{value: 13}` with the distance as an integer. The
`GrovePi.Ultrasonic` module works by polling
the pin that you have registered to a Ultrasonic sensor.
Example use:
```
iex> pin = 3
iex> {:ok, pid} = GrovePi.Ultrasonic.start_link(pin)
{:ok, #PID<0.205.0>}
iex> GrovePi.Ultrasonic.read_distance(pin)
iex> GrovePi.Ultrasonic.read(pin)
20
iex> GrovePi.Ultrasonic.read_distance(pin)
iex> GrovePi.Ultrasonic.read(pin)
23
iex> GrovePi.Ultrasonic.subscribe(7, :changed)
:ok
```
"""

@type distance :: integer
alias GrovePi.Board
alias GrovePi.Registry.Pin

defmodule State do
@moduledoc false
defstruct [:pin, :prefix]
end
The `GrovePi.Ultrasonic.DefaultTrigger` is written so when the value of
the ultrasonic sensor changes, the subscribed process will receive
a message in the form of `{pid, :changed, %{value: 44}`. The
message should be received using GenServer handle_info/2.
@spec start_link(GrovePi.pin()) :: Supervisor.on_start()
def start_link(pin, opts \\ []) do
prefix = Keyword.get(opts, :prefix, Default)
opts = Keyword.put(opts, :name, Pin.name(prefix, pin))
GenServer.start_link(__MODULE__, [pin, prefix], opts)
For example:
```
def handle_info({_pid, :changed, %{value: value}}, state) do
# do something with value
{:noreply, state}
end
"""

def init([pin, prefix]) do
{:ok, %State{pin: pin, prefix: prefix}}
end
@type distance :: integer

@doc false
@deprecated "Use GrovePi.Ultrasonic.read/1 instead"
@spec read_distance(GrovePi.pin(), atom) :: distance
def read_distance(pin, prefix \\ Default) do
GenServer.call(Pin.name(prefix, pin), {:read_distance})
GenServer.call(Pin.name(prefix, pin), :read)
end

def handle_call({:read_distance}, _from, state) do
with :ok <- Board.send_request(state.prefix, <<7, state.pin, 0, 0>>),
@doc false
@spec read_value(atom, GrovePi.pin()) :: distance
def read_value(prefix, pin) do
with :ok <- Board.send_request(prefix, <<7, pin, 0, 0>>),
:ok <- wait_for_sensor(),
<<_, distance::big-integer-size(16)>> <- Board.get_response(state.prefix, 3),
do: {:reply, distance, state}
<<_, distance::big-integer-size(16)>> <- Board.get_response(prefix, 3),
do: distance
end

defp wait_for_sensor do
Expand Down
39 changes: 39 additions & 0 deletions lib/grovepi/ultrasonic/default_trigger.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
defmodule GrovePi.Ultrasonic.DefaultTrigger do
@behaviour GrovePi.Trigger

@moduledoc """
This is the default triggering mechanism for changes to Ultrasonic reads. The
event is `:changed` and includes the trigger state. The trigger state
for the default trigger is a struct containing a `value` property.
## Examples
iex> GrovePi.Ultrasonic.DefaultTrigger.init([])
{:ok, %GrovePi.Ultrasonic.DefaultTrigger.State{value: 0}}
iex> GrovePi.Ultrasonic.DefaultTrigger.update(50, %{value: 0})
{:changed, %{value: 50}}
iex> GrovePi.Ultrasonic.DefaultTrigger.update(0, %{value: 50})
{:ok, %{value: 50}}
iex> GrovePi.Ultrasonic.DefaultTrigger.update(125, %{value: 50})
{:changed, %{value: 125}}
The value 0 is an error and therefore discarded.
"""

defmodule State do
@moduledoc false
defstruct value: 0
end

def init(_) do
{:ok, %State{}}
end

def update(value, %{value: value} = state), do: {:ok, state}

def update(value, state) when value == 0, do: {:ok, state}

def update(new_value, state), do: {:changed, %{state | value: new_value}}
end
4 changes: 4 additions & 0 deletions test/grovepi/ultrasonic/default_trigger_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
defmodule GrovePi.Ultrasonic.DefaultTriggerTest do
use ExUnit.Case, async: true
doctest GrovePi.Ultrasonic.DefaultTrigger
end
26 changes: 23 additions & 3 deletions test/grovepi/ultrasonic_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,36 @@ defmodule GrovePi.UltrasonicTest do
use ComponentTestCase, async: true

setup %{prefix: prefix} = tags do
{:ok, _} = GrovePi.Ultrasonic.start_link(@pin, prefix: prefix)
poll_interval = Map.get(tags, :poll_interval, 1)

{:ok, _} =
GrovePi.Ultrasonic.start_link(
@pin,
poll_interval: poll_interval,
prefix: prefix
)

{:ok, tags}
end

test "gets distance", %{prefix: prefix, board: board} do
test "gets distance with read/2", %{prefix: prefix, board: board} do
distance = 20

GrovePi.I2C.add_response(board, <<1, distance::big-integer-size(16)>>)

assert distance == GrovePi.Ultrasonic.read_distance(@pin, prefix)
assert distance == GrovePi.Ultrasonic.read(@pin, prefix)
assert <<7, @pin, 0, 0>> == GrovePi.I2C.get_last_write_data(board)
end

@tag :capture_log
test "receives message after subscribe", %{prefix: prefix, board: board} do
GrovePi.Ultrasonic.subscribe(@pin, :changed, prefix)

GrovePi.I2C.add_responses(board, [
10,
20
])

assert_receive {@pin, :changed, _}, 300
end
end

0 comments on commit 7080639

Please sign in to comment.