diff --git a/README.md b/README.md index c51232f..33b3c7f 100644 --- a/README.md +++ b/README.md @@ -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 + + + +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}} +``` + -**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: + + + + * `[: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 -``` \ No newline at end of file +Or do whatever else it is that you cool cats 😸 do with telemetry 😉 diff --git a/lib/dht.ex b/lib/dht.ex index f9ae3e1..f1e7af1 100644 --- a/lib/dht.ex +++ b/lib/dht.ex @@ -1,8 +1,8 @@ defmodule DHT do @moduledoc File.read!("README.md") - |> String.split(~r//) - |> Enum.drop(1) - |> Enum.join("\n") + |> String.split(~r//) + |> Enum.drop(1) + |> Enum.join("\n") @type period :: non_neg_integer() @type pin :: non_neg_integer() @@ -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//) - |> Enum.drop(1) - |> hd()} + #{ + File.read!("README.md") + |> String.split(~r//) + |> 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("") - |> Enum.drop(1) -|> hd()} + #{ + File.read!("README.md") + |> String.split("") + |> Enum.drop(1) + |> hd() + } ) @spec read(pin(), sensor()) :: {:ok, reading} | {:error, %ArgumentError{}} | {:error, integer()} def read(pin, sensor) do @@ -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}} @@ -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)) diff --git a/lib/dht/application.ex b/lib/dht/application.ex index 1b1e698..32de54f 100644 --- a/lib/dht/application.ex +++ b/lib/dht/application.ex @@ -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, diff --git a/lib/dht/telemetry.ex b/lib/dht/telemetry.ex index d74f687..a4c7381 100644 --- a/lib/dht/telemetry.ex +++ b/lib/dht/telemetry.ex @@ -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) @@ -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 diff --git a/mix.exs b/mix.exs index b66e061..a415f0b 100644 --- a/mix.exs +++ b/mix.exs @@ -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 @@ -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 ] diff --git a/mix.lock b/mix.lock index 3b2d511..f481b8c 100644 --- a/mix.lock +++ b/mix.lock @@ -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"}, }