My first Nerves project
My first attempt at embedded Elixir
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.
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.
- Make the rpi send the temperatures to a remote server. Namely a Phoenix server.
- Use Scenic to display the temperatures locally.
- 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.