Skip to content

Commit

Permalink
update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jjcarstens committed Jun 20, 2020
1 parent 72c7e03 commit b9c60ce
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 44 deletions.
90 changes: 70 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,84 @@
# dht
Elixir implementation to read DHT11 and DHT22 sensors
# DHT

## EXPERIMENTAL
"Driver for DHT 11, DHT 22, and AM2302 temperature/humidity sensors"

This is an experiment to use only `Circuits.GPIO` to read temperature and humidity values from DHT11 and DHT22 sensors.
# Installation

See these datasheets for more info:
This ports the [Adafruit Python DHT](https://github.com/adafruit/Adafruit_Python_DHT) library
C source to handle the pin reads.

Currently this is only supporting valid Nerves targets, but in the future will be available
to use in any Elixir environment with GPIO (like rasbian).

For none supported platforms (like host machine, MacOS, etc), readings will still work but be
randomly generated.

```elixir
def deps() do
{:dht, "~> 0.1"}
end
```

See the datasheets for more info:
* [Adafruit DHT22/AM2302](https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf)
* [SparkFun DHT22](https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf)
* [Mouser DHT11](https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf)

# Usage

<!-- READDOC !-->

You need to specify the GPIO pin number and sensor type when taking a reading.
The sensor type can be a string, atom, or integer representation of the target sensor:

```elixir
iex()> DHT.read(6, :dht22)
{:ok, %{temperature: 22.6, humidity: 50.5}}

iex()> DHT.read(6, "dht22")
{:ok, %{temperature: 22.6, humidity: 50.5}}

iex()> DHT.read(6, 22)
{:ok, %{temperature: 22.6, humidity: 50.5}}

iex()> DHT.read(6, "22")
{:ok, %{temperature: 22.6, humidity: 50.5}}
```
<!-- READDOC !-->

**TL;DR**
You need to trigger pin LOW for ~ 1ms and then the sensor will start sending back pulses. The length of which corresponds to a bit value - `25-28 us` == "0" and anything longer == "1"
DHT also supports polling at regular intervals which outputs `:telemetry` events:

<!-- POLLDOC !-->

* `[:dht, :read]`
* message is a map with `:temperature` and `:humidity` keys
* metadata is map with `:pin` and `:sensor` keys
* `[:dht, :error]`
* message is a map with `:error` key containing the failure message
* metadata is map with `:pin` and `:sensor` keys

The polling period defaults to `2` seconds, which is the minimum rate allowed for
DHT sensors, but you can specify longer:

To attempt to trigger and read, run:
```elixir
{:ok, dht} = DHT.start_link
DHT.refresh # this triggers sensor to start sending data
DHT.read # shows the data captured from the last trigger attempt
# Poll DHT22 on GPIO 6 every 30 seconds
iex()> DHT.start_polling(6, :dht22, 30)
{:ok, #PID<0.233.0>}
```

This experiment is testing with `circuits_gpio` and `pigpiox`. You can switch between the two:
Once polling, you can attach to the read events view `:telemetry.attach/4`

```elixir
# circuits_gpio by default. But you can also specify
DHT.start_link(17, :circuits_gpio)
defmodule MyWatcher do
def inspect_it(a, b, c, d) do
IO.inspect(a)
IO.inspect(b)
IO.inspect(c)
IO.inspect(d)
end
end

# For pigpio
DHT.start_link(17, :pigpiox)
:telemetry.attach("im-attached", [:dht, :read], &MyWatcher.inspect_it/4, nil)
```

You can use the included `weather_man` nerves app to quickly build firmware
```elixir
$ cd weather_man && mix firmware
```
Or do whatever else it is that you cool cats 😸 do with telemetry 😉
47 changes: 28 additions & 19 deletions lib/dht.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
defmodule DHT do
@moduledoc File.read!("README.md")
|> String.split(~r/<!-- .*DOC !-->/)
|> Enum.drop(1)
|> Enum.join("\n")
|> String.split(~r/<!-- .*DOC !-->/)
|> Enum.drop(1)
|> Enum.join("\n")

@type period :: non_neg_integer()
@type pin :: non_neg_integer()
Expand All @@ -11,30 +11,39 @@ defmodule DHT do

@doc ~s(
Start polling of readings at specified period intervals that are delivered as telemtry events
#{File.read!("README.md")
|> String.split(~r/<!-- POLLDOC !-->/)
|> Enum.drop(1)
|> hd()}
#{
File.read!("README.md")
|> String.split(~r/<!-- POLLDOC !-->/)
|> Enum.drop(1)
|> hd()
}
)
@spec start_polling(pin(), sensor(), period()) :: DynamicSupervisor.on_start_child() | {:error, %ArgumentError{}}
@spec start_polling(pin(), sensor(), period()) ::
DynamicSupervisor.on_start_child() | {:error, %ArgumentError{}}
def start_polling(pin, sensor, period \\ 2)

def start_polling(pin, sensor, period) when is_integer(period) and period >= 2 do
case sanitize_args(pin, sensor) do
{:ok, pin, sensor} ->
DHT.Telemetry.start_polling(pin, sensor, period)
err -> err

err ->
err
end
end

def start_polling(_pin, _sensor, _period) do
{:error, %ArgumentError{message: "time period must be >= 2 seconds"}}
end

@doc ~s(
Take a reading on the specified pin for a sensor type
#{File.read!("README.md")
|> String.split("<!-- READDOC !-->")
|> Enum.drop(1)
|> hd()}
#{
File.read!("README.md")
|> String.split("<!-- READDOC !-->")
|> Enum.drop(1)
|> hd()
}
)
@spec read(pin(), sensor()) :: {:ok, reading} | {:error, %ArgumentError{}} | {:error, integer()}
def read(pin, sensor) do
Expand All @@ -54,8 +63,7 @@ defmodule DHT do

defp sanitize_args(pin, sensor) do
with {:ok, pin} <- sanitize_pin(pin),
{:ok, sensor} <- sanitize_sensor(sensor)
do
{:ok, sensor} <- sanitize_sensor(sensor) do
{:ok, pin, sensor}
else
{:error, msg} -> {:error, %ArgumentError{message: msg}}
Expand All @@ -65,10 +73,11 @@ defmodule DHT do
defp sanitize_pin(pin) when is_integer(pin), do: {:ok, pin}
defp sanitize_pin(pin), do: {:error, "invalid pin: #{inspect(pin)}"}

@supported_sensors Enum.reduce([{:dht11, 11}, {:dht22, 22}, {:am2302, 22}], [], fn {k, v}, acc ->
[{to_string(k), v}, {to_string(v), v} | acc]
end)
|> Map.new()
@supported_sensors Enum.reduce([{:dht11, 11}, {:dht22, 22}, {:am2302, 22}], [], fn {k, v},
acc ->
[{to_string(k), v}, {to_string(v), v} | acc]
end)
|> Map.new()

defp sanitize_sensor(sensor) do
cleansed = String.downcase(to_string(sensor))
Expand Down
2 changes: 2 additions & 0 deletions lib/dht/application.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
defmodule DHT.Application do
@moduledoc false
use Application

def start(_type, _args) do
opts = [strategy: :one_for_one, name: DHT.Supervisor]

children = [
DHT.Port,
DHT.Telemetry,
Expand Down
2 changes: 2 additions & 0 deletions lib/dht/telemetry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule DHT.Telemetry do
period: :timer.seconds(period),
name: via_name(pin, sensor)
]

spec = :telemetry_poller.child_spec(opts)

DynamicSupervisor.start_child(__MODULE__, spec)
Expand All @@ -37,6 +38,7 @@ defmodule DHT.Telemetry do
case DHT.read(pin, sensor) do
{:ok, reading} ->
:telemetry.execute([:dht, :read], reading, meta)

{:error, err} ->
:telemetry.execute([:dht, :failure], %{error: err}, meta)
end
Expand Down
17 changes: 12 additions & 5 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ defmodule Dht.MixProject do
deps: deps(),
description: description(),
docs: docs(),
package: package()
package: package(),
preferred_cli_env: %{
docs: :docs,
"hex.build": :docs,
"hex.publish": :docs
}
]
end

Expand All @@ -30,19 +35,21 @@ defmodule Dht.MixProject do

defp deps do
[
{:ex_doc, "~> 0.22", only: :docs},
{:circuits_gpio, "~> 0.4"},
{:elixir_make, "~> 0.6"}
{:elixir_make, "~> 0.6"},
# {:telemetry_poller, "~> 0.5"},
# TODO: remove this once https://github.com/beam-telemetry/telemetry_poller/pull/47 merged
{:telemetry_poller, github: "jjcarstens/telemetry_poller"}
]
end

defp description() do
"Drive of DHT 11 and DHT 22 (temperature and humidity sensor)"
"Driver for DHT 11, DHT 22, and AM2302 temperature/humidity sensors"
end

defp docs do
[
extras: ["README.md"],
main: "readme",
source_ref: "v#{@version}",
source_url: @source_url
]
Expand Down
7 changes: 7 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
%{
"circuits_gpio": {:hex, :circuits_gpio, "0.4.2", "becda6b468271a2dea0c8a500b23b2b5d425501a7d7664c55f96e292af8aef30", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "d30787fd140bd09c6bd7fad8f3e7da6a1fa475eb3a6aff5f0c267c6f98cfcd47"},
"earmark": {:hex, :earmark, "1.4.5", "62ffd3bd7722fb7a7b1ecd2419ea0b458c356e7168c1f5d65caf09b4fbdd13c8", [:mix], [], "hexpm", "b7d0e6263d83dc27141a523467799a685965bf8b13b6743413f19a7079843f4f"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"},
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
"pigpiox": {:hex, :pigpiox, "0.1.2", "efbd55fb4b4507aaf44193cf6bf5fbdff288a351bdfed3f31bdc9377ee5623fd", [:mix], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.0", "4770888ef85599ead39c7f51d6b4b62306e602d96c69b2625d54dea3d9a5204b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69e4e8e65b0ae077c9e14cd5f42c7cc486de0e07ac6e3409e6f0e52699a7872c"},
}

0 comments on commit b9c60ce

Please sign in to comment.