Distributed programming made easy with Elixir
Multinode RPC Requests
The goal for the next project is to have a PC node query Pi nodes for diagnostic information. This project is a little different from the earlier project, in that a module is loaded on the Raspberry Pi to send back custom status messages (Figure 5).
To get the Raspberry PI's CPU, for example, with a Bash command, use:
# Bash command to get the Pi CPU temperature $ /opt/vc/bin/vcgencmd measure_temp temp=42.3'C
This Bash command can be incorporated into a small Elixir script (Listing 2) that is loaded and compiled on each of the Pi nodes. The PI_stats
module in the PI_stats.ex
script contains the function cpu_temp
, which returns a string containing the Pi node name and the output from the shell command to get the CPU temperature.
Listing 2
PI_stats.ex
01 #------------ 02 # PI_stats.ex - Get Some Stats 03 #------------ 04 defmodule PI_stats do 05 def cpu_temp() do 06 "#{Node.self()} #{:os.cmd(:"/opt/vc/bin/vcgencmd measure_temp")}" 07 end 08 # Add more diagnostics like: available RAM, idle time ... 09 end
To compile Elixir scripts, use the elexirc
command; then, their modules are available to iex
shells called from that directory. The code to compile and then test the PI_stats
module from a Raspberry Pi node is:
## compile an Elixir script $ elixirc PI_stats.ex ## test the PI_stats.cpu_temp function locally $ iex --name pi3@192.168.0.105 --cookie pitest iex> PI_stats.cpu_temp() {"pi3@192.168.0.105 temp=47.8\'C\n'}
An Erlang :rpc.multicall
function can be used on the PC node to retrieve the Pi CPU temperatures. This function is passed the node list, module name, function call, and any additional arguments:
iex> :rpc.multicall( [:"pi3@192.168.0.105", :"pi4@192.168.0.101"], PI_stats, :cpu_temp, []) {["pi3@192.168.0.105 temp=47.2'C\n", "pi4@192.168.0.101 temp=43.8'C\n"], []}
The get_temps.exs
script in Listing 3 is run on the PC to get the Raspberry Pi CPU temperatures and present the data in a Zenity dialog.
Listing 3
get_temps.exs
01 #---------------------------------------- 02 # get_temps.exs - get PI CPU temperatures 03 # - show results on Zenity Dialog 04 #---------------------------------------- 05 pinodes = [ :"pi3@192.168.0.105", :"pi4@192.168.0.101"] 06 Enum.map(pinodes, fn x-> Node.connect x end) 07 08 # Get results from remote PI nodes 09 {result,_badnodes} = :rpc.multicall( pinodes, PI_stats, :cpu_temp, []) 10 11 # Format the output for a Zenity info dialog 12 output = Enum.map(result, fn x -> x end) |> Enum.join 13 :os.cmd(:"zenity --info --text=\"#{output}\" --title='Pi Diagnostics'")
To make the code more flexible, all the Pi nodes are stored in a list (pinodes
). The Eum.map
function iterates over the Pi node list and connects to each node.
The results from the RPC multicall are a little messy, so the Enum.map
and Enum.join
functions format the results into one long string that is passed to a Zenity info dialog box.
As in the earlier project, the Elixir script is run with the common project cookie with a unique username (Figure 6).
Note that once the PI_stats.ex
script is compiled on the Pi nodes, no other action is required; as in the first project, the RPC request is processed by the underlying Erlang VM.
Data Sharing Between Nodes
Elixir offers a number of data storage options. For simple multinode data sharing, I found that the Erlang :mnesia
package for the Mnesia database management system to be a good fit. In this last project, I set up a shared schema between the three nodes (Figure 7); the Pi nodes populate tables with their GPIO pin status every two seconds.
On the PC, I use the first project to write to the GPIO pins, and I create a new script to monitor the status of the pins within a Mnesia shared table. The :mnesia.create_schema
function creates a shared schema for all the listed nodes. To create a shared or distributed schema, Mnesia needs to be stopped on all nodes; then, after the schema is created, Mnesia is restarted. The :rpc.multicall
function is extremely useful when identical actions need to occur on distributed nodes:
iex> # Create a distributed schema iex> allnodes = [ :"pete@192.168.0.120" , :"pi3@192.168.0.105", :"pi4@192.168.0.101"] iex> :rpc.multicall( allnodes, :mnesia, :stop, []) iex> :mnesia.create_schema(allnodes) iex> :rpc.multicall( allnodes, :mnesia, :start, [])
If a schema already exists, you need to delete it with the :mnesia.delete_schema([node()])
call before a new one can be created.
After creating a shared schema, the next step is to add a table (Pi3) of GPIO pin values for the Raspberry Pi 3:
iex> :mnesia.create_table(Pi3, [attributes: [ :gpio, :value] ])
For nodes that are writing to a specific table, the table should be defined as both a RAM and disk copy. To do this, log in to that node and enter:
iex> :mnesia.change_table_copy_type(Pi3, node(), :disc_copies)
For large projects in which multiple nodes are reading and writing into tables, you should use transaction statements. For small projects that involve just one node writing into a table, you can use "dirty" reads and writes (i.e., uncommitted data in a database). To write the value 1 for pin 4 into the Pi3 table and read the record back, use:
iex> :mnesia.dirty_write({Pi3, 4,1}) :ok iex> pin4val = :mnesia.dirty_read({Pi3, 4}) [{Pi3, 4, 1}]
Now that you can make simple writes and reads, the next step is to create a script that continually populates the Pi3 table with GPIO pin values.
Populating a Mnesia Table
The Elixir programming language has some interesting syntax features that allow you to write efficient code. Two features that will help streamline a table input function are anonymous and enumeration functions.
The ampersand (&
) character creates a short hard (anonymous function) that can be created on the fly. The following code shows simple and complex examples that read a GPIO pin value and remove the trailing newline character:
iex> # A basic example iex> sum = &(&1 + &2) iex> sum.(2, 3) 5 iex> getpin=&(:os.cmd(:"gpio read #{(&1)} | tr -d \"\n\" ") ) iex> getpin.(7) '1'
The Enum.map
function can implement complex for-each loops. These two Elixir features together can read 27 Raspberry Pi GPIO pins and write data to a Mnesia table. The Gpio3write.exs
script in Listing 4 writes GPIO values into a Mnesia table every two seconds.
Listing 4
Gpio3write.exs
01 #--------------- 02 # Gpio3write.exs - Write Pi 3 GPIO values into Mnesia every 2 seconds 03 #--------------- 04 defmodule Gpio3write do 05 def do_write do 06 getpin=&(:os.cmd(:"gpio read #{(&1)} | tr -d \"\n\" ") ) 07 Enum.map(0..26, fn x-> :mnesia.dirty_write({Pi3, x, getpin.(x) }) end) 08 :timer.sleep(2000) 09 do_write() 10 end 11 end 12 # Start Mnesia 13 :mnesia.start() 14 # Cycle every 2 seconds and write values 15 Gpio3write.do_write()
The command
$ elixir --name pi3@192.168.0.105 --cookie pitest Gpio3write.exs
starts the script on the Pi node.
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Juno Tab 3 Launches with Ubuntu 24.04
Anyone looking for a full-blown Linux tablet need look no further. Juno has released the Tab 3.
-
New KDE Slimbook Plasma Available for Preorder
Powered by an AMD Ryzen CPU, the latest KDE Slimbook laptop is powerful enough for local AI tasks.
-
Rhino Linux Announces Latest "Quick Update"
If you prefer your Linux distribution to be of the rolling type, Rhino Linux delivers a beautiful and reliable experience.
-
Plasma Desktop Will Soon Ask for Donations
The next iteration of Plasma has reached the soft feature freeze for the 6.2 version and includes a feature that could be divisive.
-
Linux Market Share Hits New High
For the first time, the Linux market share has reached a new high for desktops, and the trend looks like it will continue.
-
LibreOffice 24.8 Delivers New Features
LibreOffice is often considered the de facto standard office suite for the Linux operating system.
-
Deepin 23 Offers Wayland Support and New AI Tool
Deepin has been considered one of the most beautiful desktop operating systems for a long time and the arrival of version 23 has bolstered that reputation.
-
CachyOS Adds Support for System76's COSMIC Desktop
The August 2024 release of CachyOS includes support for the COSMIC desktop as well as some important bits for video.
-
Linux Foundation Adopts OMI to Foster Ethical LLMs
The Open Model Initiative hopes to create community LLMs that rival proprietary models but avoid restrictive licensing that limits usage.
-
Ubuntu 24.10 to Include the Latest Linux Kernel
Ubuntu users have grown accustomed to their favorite distribution shipping with a kernel that's not quite as up-to-date as other distros but that changes with 24.10.