We at Code & Context, a new bachelor's degree at the Technical University of Cologne, love project based work. During the still ongoing lockdown we want to build some kind of connection to our open space at our campus.
To finish off the second semester we have a project with the following vision: "Remote Explorer". Giving not only us, but also future students or complete outsiders an option to explore our campus would be pretty cool and might give us the feeling of being more connected.
We decided to build an IoT birdhouse with Elixir Nerves & Phoenix LiveView.
Elixir gave me a different perspective to programming and I will be going to use it as much as possible in future projects. Links to my recommended learning resources are attached at the end of this post.
Try it out yourself
You can find the code at the following git repository
When following the guide, make sure to don't forget our --live while leaving out the --no-webpack attribute:
mix phx.new bird_app_ui --no-ecto--live
Setting up WiFi and updating via SSH
Nerves provides us with an easy way to setup updates via SSH, so we do not have to constantly swap the SD card from our development device to our raspberry pi.
Updating the WiFi config
First lets update our WiFi configuration so we can use environment variables for our SSID and PSK:
# bird_app_firmware/config/target.exs# ...# Configure the network using vintage_net# See <https://github.com/nerves-networking/vintage_net> for more informationconfig:vintage_net,regulatory_domain:"US",config:[{"usb0",%{type:VintageNetDirect}},{"eth0",%{type:VintageNetEthernet,ipv4:%{method::dhcp}}},{"wlan0",%{type:VintageNetWiFi,vintage_net_wifi:%{key_mgmt::wpa_psk,ssid:System.get_env("NERVES_NETWORK_SSID"),psk:System.get_env("NERVES_NETWORK_PSK")},ipv4:%{method::dhcp}}}]# ...
To setup our environment variables execute the following commands in your terminal:
Now we can add the nerves_firmware_ssh package to the list of our mix dependencies
# bird_app_firmware/mix.exs# ...defpdepsdo[# Dependencies for all targets{:nerves_firmware_ssh,"~> 0.3",targets:@all_targets},{:bird_app_ui,path:"../bird_app_ui"},{:nerves,"~> 1.6.0",runtime:false},# ...]end# ...
now cd into your bird_app_firmware and run
mixdeps.getmixfirmware# (Connect the SD card)mixfirmware.burn
from this point on, after inserting the SD card into your raspberry,you can reach your raspberry pi (if you are in the same local network) via ssh nerves.local to debug and also deploy updates from bird_app_firmware via
# create new firmwaremixfirmware# upload firmware via sshmixupload
Picam - Setting up a video stream
We are going to use the Pi NoIR Camera V2 Module for our Project.
Elixir library used to capture MJPEG video on a Raspberry Pi using the camera module.
Picam
Picam is an Elixir library that provides a simple API for streaming MJPEG video and capturing JPEG stills using the camera module on Raspberry Pi devices running Linux.
Features currently supported by the API:
Set sharpness, contrast, brightness, saturation, ISO, and shutter speed values
Set the exposure, sensor, metering, and white balance modes
Set image and color effects
Rotate and flip the image vertically and horizontally
Set the exposure compensation (EV) level
Change the image size
Adjust JPEG fidelity through quality level, restart intervals, and region of interest
Enable or disable video stabilization
Adjust the video framerate
Render fullscreen or windowed video preview to HDMI and CSI displays
For specifics on the above features, please consult the Hex docs.
To update the firmware cd into bird_app_firmware and run mix firmware && mix upload.
Now you should be able to access nerves.local/video.mjpeg. But there is still something wrong. After 60s we run into a timeout because of the standard configuration. To fix this, we need to change our idle_timeout to infinity.
# bird_app_firmware/config/target.exsuseMix.Config# When we deploy to a device, we use the "prod" configuration:import_config"../../bird_app_ui/config/config.exs"import_config"../../bird_app_ui/config/prod.exs"config:bird_app_ui,BirdAppUiWeb.Endpoint,# Nerves root filesystem is read-only, so disable the code reloadercode_reloader:false,http:[port:80,protocol_options:[idle_timeout::infinity]],# Use compile-time Mix config instead of runtime environment variablesload_from_system_env:false,# Start the server since we're running in a release instead of through `mix`server:true,url:[host:"nerves.local",port:80]#...
Now we have a perfect continious live stream from our picam.
Adding a snapshot plug for single images
To easily add a route for single images of our video, we can add a plug which makes use of our next_frame() function
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:
We are going to make use of a GenServer once again and we will start polling data every 2 seconds with DHT.start_polling(4, :dht22, 2), as soon as the GenServer is initialized, which will also allow us to make use of telemetry events.
If you happen to see a bird live on stream it might be a good idea to save the moment. I am going to show you how to implement a simple telegram bot to your app.
I am going to use the nadia telegram bot hex package.
When we click the button, the component sends the snap event to itself where we first write a temporary snap.jpg file with the contents of BirdAppHardware.Camera.next_frame() which we can finally send to telegram.
Final word
I am honestly having a lot of fun developing and building things with Elixir and will do a lot more in the future.
There are still a few todos like a real time bird detection on my list. We also have to implement everything into the following physical birdhouse and 3d-print some additional assets, like a picam holder and a food container.
I did not go through each and every component in detail. If you are interested in the development of the app, check out the git repository and try it out!