The project - Reading temperatures of 2 ds18b20 sensors

I've had a setup running with 2 ds18b20 sensors connected to an  Arduino Uno that was connected to a Raspberry Pi 3. I used  johnnyfive.io, Influx DB and Grafana to read, store and show the  temperature over time.

Since I'd implemented this once it seamed like a good place to start for my first Nerves project.

Inspiration

I found an article by a guy who did pretty much the same, but with one sensor and without supervision. The Github-user "Developerworks" also did this but with one sensor. I also drew inspiration from Justin Schneck's kegarator repo.

Step 1: The hardware

I pretty much copied what "Developerworks" did with his sensor. I just attached two sensors to the breadboard. I'm not sure if this is the best way to do it as this type sensor need a resistor between the rpi and itself.  I do get a small temperature difference (~ 0.5 degrees C) between the sensors, and I'm thinking it might have something to do with this.

The wiring.

Red goes to pin 1 (3v3), blue (or black) goes to ground on pin 6 and yellow is data on GPIO(4) on pin 7.

I connected a monitor to the rpi via HDMI since I had a spare one and I thought it would be easier to work on multiple monitors.

Step 2: Reading the temperature and printing it to screen

Instead of reading the temperature directly in an Elixir Application I chose to do it from a GenServer. The GenServer gets a list of sensors each represented as a file in /sys/bus/w1/devices/. Each of their names starts with "28-" so we can use Enum.filter() and String.starts_with?() to get them.

  defp get_sensors do
    File.ls!(@base_dir)
    |> Enum.filter(&(String.starts_with?(&1, "28-")))
  end

This is used to initialize the state of the GenServer:

  def init(_opts) do
    sensors = get_sensors()
    send(self(), :read_temp)
    {:ok, %{
        sensors: sensors,
        temperatures: %{}
    }}
  end

send(self(), :read_temp) gets handled by handle_info(:read_temp, state) . This reads the files (sensors) present in /sys/bus/w1/devices/ and updates the state of the GenServer. It then sends a message to itself after @interval microseconds to read and update again.

def handle_info(:read_temp, state) do
    temps =
      state[:sensors]
      |> Enum.with_index
      |> Enum.map(&read_temp(&1, @base_dir))
      |> IO.inspect

    state = %{state | temperatures: temps}
    Process.send_after(self(), :read_temp, @interval)
    {:noreply, state}
end

The temperature itself is read with the following function:

defp read_temp({sensor, index}, base_dir) do
  sensor_data = File.read!("#{base_dir}#{sensor}/w1_slave")
  {temp, _} = Regex.run(~r/t=(\d+)/, sensor_data)
  |> List.last
  |> Float.parse

  atom =
    "sensor_" <> Integer.to_string(index)
    |> String.to_atom

  %{atom => temp / 1000}
end

The complete GenServer

The GenServer as a whole looks like this:

Application

The GenServer is then registered in the Application that  was generated with mix nerves.new

The result

Where's a short video of the monitor output from this code.

Next steps

I've got a few ideas on how to take this project further.

  1. Make the rpi send the temperatures to a remote server. Namely a Phoenix server.
  2. Use Scenic to display the temperatures locally.
  3. Make a library to make it simpler to read sensors with Elixir and Nerves

I'll have a go at the remote server first!

To be continued

The fulle repo

The full code lives at Gitlab.com

https://gitlab.com/smedegaard/extemp

Contact

I'm smedegaard on the #nerves channel of elixir-lang.slack.com. Feel free to contact me there.