adkron / grovepi

Use the GrovePi in Elixir
Apache License 2.0
47 stars 9 forks source link

Add poller for DHT11 sensor #21

Closed axelclark closed 7 years ago

axelclark commented 7 years ago

Add poller support for a DHT11 sensor. An event is created for any change to the humidity or temperature values. The DHT11 is read using the DHT module. The module will only work with DHT module_type 0 which is the sensor that comes with the GrovePi+ Starter Kit.

This is a work in progress.

I'm trying to build the Home Weather Station project which uses a DHT and the RGBLCD. https://github.com/DexterInd/GrovePi/blob/master/Projects/Home_Weather_Display/Home_Weather_Display.py

axelclark commented 7 years ago

I'm running into a little problem with the test because the DHT module I want to read from uses a GenServer while the Digital and Analog modules used previously for the pollers only contain functions to read from the board.

I started to figure out how to start the DHT GenServer in my tests, but then I was wondering why the DHT module combines the GenServer with reading from the board while the other two do not.

Here is my test error:

  1) test reading gets from the grovepi board (GrovePi.DHT11Test)
     test/dht_11_test.exs:32
     ** (EXIT from #PID<0.279.0>) exited in: GenServer.call({:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000)
         ** (EXIT) process attempted to call itself

21:24:39.559 [error] GenServer {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5} terminating
** (stop) exited in: GenServer.call({:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000)
    ** (EXIT) process attempted to call itself
    (elixir) lib/gen_server.ex:731: GenServer.call/3
    (grovepi) lib/grovepi/poller.ex:135: GrovePi.DHT11.update_value/1
    (grovepi) lib/grovepi/poller.ex:123: GrovePi.DHT11.handle_call/3
    (stdlib) gen_server.erl:615: :gen_server.try_handle_call/4
    (stdlib) gen_server.erl:647: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: :read
State: %GrovePi.DHT11.State{pin: 5, poll_interval: 1000000, poll_reference: #Reference<0.0.2.1203>, prefix: :"01:24:39.514142Elixir.ComponentTestCase", trigger: GrovePi.DHT11.DefaultTrigger, trigger_state: %GrovePi.DHT11.DefaultTrigger.State{humidity: 0, temp: 0}}

21:24:39.559 [error] GenServer #PID<0.280.0> terminating
** (stop) exited in: GenServer.call({:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000)
    ** (EXIT) process attempted to call itself
Last message: {:EXIT, #PID<0.279.0>, {:calling_self, {GenServer, :call, [{:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000]}}}
State: {:state, {#PID<0.280.0>, GrovePi.Supervisor}, :one_for_one, [{:child, #PID<0.286.0>, GrovePi.Board, {GrovePi.Board, :start_link, [64, :"01:24:39.514142Elixir.ComponentTestCase"]}, :permanent, 5000, :worker, [GrovePi.Board]}, {:child, #PID<0.284.0>, GrovePi.Registry.Subscriber, {GrovePi.Registry.Subscriber, :start_link, [:"01:24:39.514142Elixir.ComponentTestCase"]}, :permanent, :infinity, :supervisor, [GrovePi.Registry.Subscriber]}, {:child, #PID<0.281.0>, GrovePi.Registry.Pin, {GrovePi.Registry.Pin, :start_link, [:"01:24:39.514142Elixir.ComponentTestCase"]}, :permanent, :infinity, :supervisor, [GrovePi.Registry.Pin]}], :undefined, 3, 5, [], 0, GrovePi.Supervisor, [64, :"01:24:39.514142Elixir.ComponentTestCase"]}
adkron commented 7 years ago

Dht just wasn't updated to polling yet. This was the old way before the poller. The dht module probably shouldn't be relied upon.

Amos King Binary Noggin

On May 17, 2017, at 20:38, Axel Clark notifications@github.com wrote:

I'm running into a little problem with the test because the DHT module I want to read from uses a GenServer while the Digital and Analog modules used previously for the pollers only contain functions to read from the board.

I started to figure out how to start the DHT GenServer in my tests, but then I was wondering why the DHT module combines the GenServer with reading from the board while the other two do not.

Here is my test error:

1) test reading gets from the grovepi board (GrovePi.DHT11Test) test/dht_11_test.exs:32 (EXIT from #PID<0.279.0>) exited in: GenServer.call({:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000) (EXIT) process attempted to call itself

21:24:39.559 [error] GenServer {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5} terminating (stop) exited in: GenServer.call({:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000) (EXIT) process attempted to call itself (elixir) lib/gen_server.ex:731: GenServer.call/3 (grovepi) lib/grovepi/poller.ex:135: GrovePi.DHT11.update_value/1 (grovepi) lib/grovepi/poller.ex:123: GrovePi.DHT11.handle_call/3 (stdlib) gen_server.erl:615: :gen_server.try_handle_call/4 (stdlib) gen_server.erl:647: :gen_server.handle_msg/5 (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3 Last message: :read State: %GrovePi.DHT11.State{pin: 5, poll_interval: 1000000, poll_reference: #Reference<0.0.2.1203>, prefix: :"01:24:39.514142Elixir.ComponentTestCase", trigger: GrovePi.DHT11.DefaultTrigger, trigger_state: %GrovePi.DHT11.DefaultTrigger.State{humidity: 0, temp: 0}}

21:24:39.559 [error] GenServer #PID<0.280.0> terminating (stop) exited in: GenServer.call({:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000) (EXIT) process attempted to call itself Last message: {:EXIT, #PID<0.279.0>, {:calling_self, {GenServer, :call, [{:via, Registry, {:"01:24:39.514142Elixir.ComponentTestCase.Elixir.GrovePi.Registry.Pin", 5}}, {:read_temp_and_humidity, 0}, 5000]}}} State: {:state, {#PID<0.280.0>, GrovePi.Supervisor}, :one_for_one, [{:child, #PID<0.286.0>, GrovePi.Board, {GrovePi.Board, :start_link, [64, :"01:24:39.514142Elixir.ComponentTestCase"]}, :permanent, 5000, :worker, [GrovePi.Board]}, {:child, #PID<0.284.0>, GrovePi.Registry.Subscriber, {GrovePi.Registry.Subscriber, :start_link, [:"01:24:39.514142Elixir.ComponentTestCase"]}, :permanent, :infinity, :supervisor, [GrovePi.Registry.Subscriber]}, {:child, #PID<0.281.0>, GrovePi.Registry.Pin, {GrovePi.Registry.Pin, :start_link, [:"01:24:39.514142Elixir.ComponentTestCase"]}, :permanent, :infinity, :supervisor, [GrovePi.Registry.Pin]}], :undefined, 3, 5, [], 0, GrovePi.Supervisor, [64, :"01:24:39.514142Elixir.ComponentTestCase"]} — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

axelclark commented 7 years ago

Would the update for polling primarily remove the GenServer? Something similar to the following?

def read_temp_and_humidity(prefix, pin, module_type \\ 0) do
  with :ok <- Board.send_request(prefix, <<40, pin, module_type, 0>>)
       <<_, temp::little-float-size(32), humidity::little-float-size(32)>> <-
         Board.get_response(state.prefix, 9),
       do: {temp, humidity}
end

def read_temp_and_humidity(pin, module_type \\ 0) do
  read_temp_and_humidity(Default, pin, module_type)
end
adkron commented 7 years ago

I'm on my phone but that looks right. I would just make a read function and put everything inside the poller. The trigger that is used would determine if temp, humidity, or both are sent or trigger. The actual poller module just needs to know how to read the sensor.

Amos King Binary Noggin

On May 17, 2017, at 21:20, Axel Clark notifications@github.com wrote:

Would the update for polling primarily remove the GenServer? Something similar to the following?

def read_temp_and_humidity(prefix, pin, module_type \ 0) do with :ok <- Board.send_request(prefix, <<40, pin, moduletype, 0>>) <<, temp::little-float-size(32), humidity::little-float-size(32)>> <- Board.get_response(state.prefix, 9), do: {temp, humidity} end

def read_temp_and_humidity(pin, module_type \ 0) do read_temp_and_humidity(Default, pin, module_type) end — You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

axelclark commented 7 years ago

Got it...I'll try it out. Thanks!

sourcelevel-bot[bot] commented 7 years ago

Ebert has finished reviewing this Pull Request and has found:

You can see more details about this review at https://ebertapp.io/github/fhunleth/grovepi/pulls/21.

axelclark commented 7 years ago

Initial attempt. Haven't tested on hardware because I'm traveling...up in NYC for Empex.

fhunleth commented 7 years ago

@adkron I totally agree about pulling this in and then iterating. I've been away the past couple days, but my brief scans of these PRs makes me think that this is the right decision.

@axelclark Thank you so much for making a pass on the additional sensors and adding examples.

fhunleth commented 7 years ago

@axelclark and @adkron I've been so busy that I haven't been reviewing these properly or I would have noticed this earlier. First, is there a reason that the existing DHT module hasn't been removed in preference to this one? Also, the module type should be configurable so that it's not hardcoded to the DHT11. Here are the module_type options: https://github.com/DexterInd/GrovePi/blob/master/Firmware/Source/v1.2/grove_pi_v1_2_7/grove_pi_v1_2_7.ino#L136

axelclark commented 7 years ago

@fhunleth the DHT module is setup to do single reads and is not compatible with polling. The DHT11 implements polling and isn't a direct replacement for the DHT module. I think the DHT module needs to be refactored to remove the GenServer and just have functions that do reads. I have hard coded the read function into DHT11.read_value/2, but it should probably call a function in the DHT module.

The DHT11 module is hard-coded to module 0 because it's not possible to pass options into the current implementation of Polling.read_value/2. It needs to be updated to allow options to be passed in. @adkron mentioned this issue above and suggested we add DHT11 and work on allowing options to be injected into Poller in the future.

fhunleth commented 7 years ago

Thanks. I see the comment about hardcoding module 0 and adding options, now.

I think that we should delete DHT and rename DHT11 to DHT. I realize that they aren't API compatible, but you can still poll by calling DHT11.read/2 and setting a long poll interval. Or did I miss something? It seems like DHT11 is almost completely superior to DHT except for the module_type issue that probably only a couple people will understand anyway.

adkron commented 7 years ago

What if we made polling 0 not poll. Actually I think I already did that.

Amos King Binary Noggin

On Jun 2, 2017, at 20:39, Frank Hunleth notifications@github.com wrote:

Thanks. I see the comment about hardcoding module 0 and adding options, now.

I personally think that we should delete DHT and rename DHT11 to DHT. I realize that they aren't API compatible, but you can still poll by calling DHT11.read/2 and setting a long poll interval.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

axelclark commented 7 years ago

I don't see any big issues with deleting it. The only downside is you could use DHT to read the other module types, but that may not be worth creating potential confusion with the two modules.

I actually hadn't noticed the read/2 function to do a simple read in the Polling module before. I think it allows you to do read without polling.