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
-
TUXEDO Computers Unveils Linux Laptop Featuring AMD Ryzen CPU
This latest release is the first laptop to include the new CPU from Ryzen and Linux preinstalled.
-
XZ Gets the All-Clear
The back door xz vulnerability has been officially reverted for Fedora 40 and versions 38 and 39 were never affected.
-
Canonical Collaborates with Qualcomm on New Venture
This new joint effort is geared toward bringing Ubuntu and Ubuntu Core to Qualcomm-powered devices.
-
Kodi 21.0 Open-Source Entertainment Hub Released
After a year of development, the award-winning Kodi cross-platform, media center software is now available with many new additions and improvements.
-
Linux Usage Increases in Two Key Areas
If market share is your thing, you'll be happy to know that Linux is on the rise in two areas that, if they keep climbing, could have serious meaning for Linux's future.
-
Vulnerability Discovered in xz Libraries
An urgent alert for Fedora 40 has been posted and users should pay attention.
-
Canonical Bumps LTS Support to 12 years
If you're worried that your Ubuntu LTS release won't be supported long enough to last, Canonical has a surprise for you in the form of 12 years of security coverage.
-
Fedora 40 Beta Released Soon
With the official release of Fedora 40 coming in April, it's almost time to download the beta and see what's new.
-
New Pentesting Distribution to Compete with Kali Linux
SnoopGod is now available for your testing needs
-
Juno Computers Launches Another Linux Laptop
If you're looking for a powerhouse laptop that runs Ubuntu, the Juno Computers Neptune 17 v6 should be on your radar.