Next Article in Journal
Dynamic Current-Limitation Strategy of Grid-Forming Inverters Based on SR Latches
Previous Article in Journal
Regionally Adaptive Active Learning Framework for Nuclear Segmentation in Microscopy Image
Previous Article in Special Issue
Adaptive Clustering and Scheduling for UAV-Enabled Data Aggregation
 
 
Font Type:
Arial Georgia Verdana
Font Size:
Aa Aa Aa
Line Spacing:
Column Width:
Background:
Article

Functional Programming for the Internet of Things: A Comparative Study of Implementation of a LoRa-MQTT Gateway Written in Elixir and C++

by
Philip Branch
*,† and
Phillip Weinstock
School of Science, Computing and Engineering Technologies, Swinburne University of Technology, Melbourne, VIC 3122, Australia
*
Author to whom correspondence should be addressed.
These authors contributed equally to this work.
Electronics 2024, 13(17), 3427; https://doi.org/10.3390/electronics13173427
Submission received: 21 June 2024 / Revised: 24 August 2024 / Accepted: 26 August 2024 / Published: 29 August 2024
(This article belongs to the Special Issue Ubiquitous Sensor Networks, 2nd Edition)

Abstract

:
Networks for the Internet of Things typically use a gateway to provide connectivity between a low bit rate, low capability sensor network and the broader Internet. The gateway can be subject to very high traffic loads, many concurrent processes and needs to be highly reliable. Functional programming languages such as Erlang and Elixir have proven to be an effective programming paradigm for such scenarios, notably in large-scale telecommunications switches. In this paper, we report on our experience of developing a gateway between a LoRa network and an MQTT broker using the functional programming language Elixir and the more conventional language C++. To obtain an understanding of this approach to development, we first developed an initial prototype on a single-board computer using Elixir. We then developed the same system in C++ and ran experiments to compare the two systems’ performance. In order to understand the performance of such systems on low-end IoT devices, we then developed the same system on a low-cost ESP32 micro-controller in both C++ and Elixir. We were able to run the Elixir-based system on an ESP32 micro-controller but found that its performance was significantly poorer than the same system written in C++. We conclude that functional programming has great potential for the development of IoT systems, but work needs to be carried out to improve the supporting libraries and underlying virtual machines. We also note that learning to program in a functional programming language has quite a steep learning curve.

1. Introduction

Functional programming is an approach to systems development that is intended to produce systems that are robust, concurrent, fault-tolerant and scalable. Functional programming languages such as Erlang and Elixir have been used to develop systems for telephone exchanges, banking systems and other high-availability systems [1].
Our interest in functional programming is in gateways for the Internet of Things (IoT). Networks for the IoT typically have a common architecture where a network of sensors and actuators communicate either directly or via other nodes with a gateway using a communications protocol adapted for the IoT, as shown in Figure 1. The gateway then communicates with other processes or people via the wider Internet. We see this architecture in 6LoWPAN [2], LoRaWAN [3] and ZigBee [4] amongst others. In such an architecture the gateway is a single point of failure subject to heavy traffic loads. The gateway needs to be robust, fault-tolerant, scalable and deal well with concurrency. Consequently, the capabilities of functional programming may well be a good match for programming such a gateway.
The key characteristics of functional programming is that all programs are written as compositions of functions that communicate solely via message passing, functions are stateless and mostly do not generate side effects, and their data are immutable. Ensuring each process runs independently as a composition of functions removes the need for mutual exclusion (‘mutex’) locks as found in threads in more conventional programming languages [5].
One of the challenges related to concurrency in conventional languages and frequently a source of unpredictable errors is avoiding having multiple processes update shared data. The approach in conventional languages is to have all other processes (or threads) excluded from running code that accesses that shared data until the process completes its operation.
Functional programs avoid this situation through the use of stateless functions that act only on the messages they receive and respond with another message ensuring predictable behaviour [6]. Within a function, data are immutable, meaning that, once defined, a variable cannot be modified, removing much of the source of logic errors. Generally, the only time functions generate side effects is when the programmer intentionally wants an external state to change, such as writing to a serial port.
We were interested in how easy it was to develop such a system using functional programming and whether it could be adapted to low-end devices such as the Raspberry Pi and ESP32 micro-controller. We were also interested in comparing the compactness of programs written in a functional programming language compared with a more conventional language. For comparison, we wrote the same LoRa–MQTT gateway system in C++ and Elixir and ran them on the ESP32. We were also interested in the comparative performance of systems written in the different languages. Functional programming languages usually run on a virtual machine that interprets the program at run-time, whereas C++ is a compiled language that runs directly on the underlying hardware. We expected the C++ system to have superior performance to the interpreted Elixir system but wanted some quantitative measurements of the performance of systems developed using the different languages.
We explored the capabilities of functional programming for the Internet of Things by developing a gateway that acted as a concentrator for multiple LoRa (not LoRaWAN) nodes and as an MQTT interface that published data to an MQTT broker. We chose to develop an MQTT gateway because it is widely used in the IoT, but it is worth noting it is not the only protocol used to distribute IoT data. It is also important to note that we were not aiming for a comprehensive comparison of different languages and their relative ease in developing a LoRa-MQTT gateway. We were interested in gaining insights into the strengths and weaknesses of using these two fundamentally different kinds of languages for development in the Internet of Things.
In total, we developed four systems. The first was written in Elixir to run on a Raspberry Pi using the BEAM Virtual Machine [6]. The purpose of this work was to gain some insights into the nature of functional programming in this environment and understand how challenging it is for a programmer experienced in conventional programming to learn to develop in a functional programming language. We then developed a system with the same functionality but in the conventional programming language C++ and carried out performance measurements comparing the throughput and loss of both systems.
We then developed two other systems on low-end ESP32 micro-controllers. The purpose of this work was to see whether functional programming could be performed on very low-end devices and to obtain quantitative measurements of performance comparing systems written in conventional and functional programming languages on these devices. The ESP32 is a much more limited device than a Raspberry Pi but is widely used in IoT systems. On the ESP32, we wrote an Elixir system using the AtomVM [7] and the same system in the conventional programming language C++. The architectures of the systems on the two platforms were similar and are shown in Figure 2 and Figure 3.
Pictures of the actual hardware used in the experiments are shown in Figure 4 and Figure 5.
The characteristics of the two platforms are summarised in Table 1. We used a Raspberry Pi 2 equipped with a WiFi USB dongle and an Espressif ESP32. Essentially the Raspberry Pi is a full-featured computer system but is more expensive and consumes more power than the more limited ESP32. The power consumption measures listed below are for the device to be active but not transmitting.
For the Raspberry Pi system [8], the devices that generated data that traversed the gateway were LoRa nodes [9] running on Arduino Yun micro-controllers [10]. The micro-controller was connected to the Raspberry Pi via a USB serial interface. For the ESP32 system, both the nodes generating data and the gateway were ESP32 micro-controllers [11] transmitting and receiving via the Serial Peripheral Interface (SPI)-connected Modtronix LoRa transceivers [12]. The Raspberry Pi we used was a model 3B. The LoRa transceiver we used with the ESP32 was a Modtronix inAir9B. The transceiver used with the Arduino Yun was a Semtech SX127x-based LoRa shield. In both hardware platforms, the gateway forwarded the messages using WiFi to a laptop running an MQTT client.
The functional programming language used in our work, Elixir [6], is similar to the functional programming language Erlang but is better supported in the micro-controller space, particularly with routines for reading and writing to USB serial ports. Elixir also has native support for previously developed Erlang software, offers simpler abstractions for micro-controller interaction and has well-maintained I/O libraries [13].
LoRa (Long Range) is a proprietary transmission technology developed by Semtech [3]. LoRa is most often used in association with the LoRaWAN protocol. LoRaWAN is a layer-three single hop protocol that communicates with low-powered LoRa nodes and with the wider Internet via a LoRaWAN gateway. LoRaWAN manages software updates, security, and contention through the ALOHA protocol. Depending on transmit power, antenna design and terrain, LoRa can transmit up to ten kilometres, making it attractive for situations where cellular network coverage is either too expensive or is not available [14].
However, it is not required to use LoRa with LoRaWAN. There has been considerable work in exploring the capabilities of LoRa without the overhead of LoRaWAN. In particular, it is quite feasible to use LoRa as a point-to-point protocol in mesh or relay networks [15,16,17,18]. It is also feasible to develop a gateway that simply communicates between low-level LoRa and the Internet without LoRaWAN. We have adopted this approach in this paper.
MQTT (originally MQ telemetry transport) is a publish/subscribe protocol for the transport via the Internet of low bitrate data from sensor networks [19]. MQTT has a client/broker architecture where clients publish data to a broker or are subscribed to receive data from a broker. MQTT is lightweight, supports standard security protocols such as TLS and is widely used within IoT applications.
The rest of this paper is structured as follows. Section 2 discusses work related to the use of functional programming in the IoT. There has been surprisingly little work performed in this area. Nevertheless, the potential application of functional programming to IoT has been recognised by some researchers. Section 3 discusses the systems we developed in detail. We describe the design and components of each system as well as include key code samples, which we discuss in order to highlight some of the important characteristics of functional programming in this area. In Section 4, we present metrics associated with each of the systems. We found the performance of the C++ system superior to the Elixir system and recognise that considerable work needs to be carried out to improve the VMs that Elixir runs on in these low-level devices. Section 5 summarises our experience and insights gained in developing the systems. Section 6 summarises our work and points to future work we plan to carry out in this space.

2. Related Work

The design of gateways for the IoT has been recognised as an important issue. As well as being a single point of failure, they are also a choke point whose performance governs the performance of the system overall [2,20].
Phan and Kim [21] recognised the importance of gateways as an obstacle to the take-up of Smart Home technologies within the IoT. They attributed this to the fragmentation of networking within the Smart Home ecosystem. To address this issue, they developed a software-based gateway able to be readily updated to the wide variety of Smart Home technologies. A similar issue was addressed by Jiang et al. when they looked at the design of software-defined gateways for the Industrial Internet of Things (IIoT) [22]. As in Smart Homes, there is great heterogeneity in the networks used in the IIoT and in the devices connecting to it. Jiang et al. developed a “software-defined” smart gateway able to connect to diverse industrial networks and devices but also able to deliver data on the wider Internet using diverse protocols, including MQTT and the Open Platform Communication Unified Architecture (OPC UA) [23].
Although the potential of functional programming for the gateways has been recognised, it is still largely unexplored. Nevertheless, the results that have been reported have been very impressive. Li et al. [5] developed a prototype taxi dispatch system using an MQTT broker they wrote using Elixir and compared it with a similar system developed in Rust. Under a heavy load, the Rust system experienced an error rate of 49.2% while the Elixir system experienced only a 2.85% error rate. Haenisch also used Elixir to develop a gateway (which he described as a ‘bridge’) between a ZigBee network collecting temperature and humidity data and a paper production machine that adjusted its drying temperature and airflow accordingly [24]. Haenisch wrote the same system three times and compared the code between them. The first iteration was written using C and Ruby, the second using only Ruby and the third using Elixir. The C/Ruby system was 620 lines of source code, the Ruby only system 194 lines while the Elixir system was only 68 lines of source code. He observed that “Short code without side effects (pure functional languages do not have side effects) is easier to verify for correctness than the imperative code. That means, it contains fewer errors”.
The potential of Elixir as a programming language for the development of edge computing (including gateways) has also been explored. Li and Fujita used Elixir to develop a gateway connecting a mobile network to the Internet via MQTT [25]. They noted the benefits of such systems from Elixir’s lightweight concurrency model and how it simplified the development of distributed systems. They also made the observation that Elixir, while inheriting the strengths of the earlier language Erlang, has a better array of libraries and tools.
A paper by Hasselbring et al. made the case for data flow-based systems rather than control flow-based systems for IIoT-based systems [26]. They noted that the IIoT is characterised by many more complex interactions between ‘microservices’ than is found in traditional monolithic enterprise systems. Although not explicitly identifying any languages, they noted that declarative approaches to programming (such as functional programming) are better suited to this kind of interaction than traditional imperative programming.
However, weaknesses of functional programming have also been identified, in particular their performance. Because they are interpreted at run-time rather than compiled into machine code, functional programming languages can have inferior performance when compared with systems developed in conventional languages. Pereira et al. ranked 27 programming languages in terms of their energy efficiency, execution time and memory use [27]. They wrote programs in each of the languages to carry out 13 benchmarking operations. They did not evaluate Elixir but did evaluate Erlang and other functional programming languages. On all metrics and for all benchmarks, Erlang’s performance was substantially poorer than C++’s. As the authors note, “Compiled languages tend to be … the fastest and most efficient ones”.
From the work cited here, there appears to be a good case for using functional programming in the IoT, particularly for gateways. Our work builds on this previous work, but we are particularly interested in the practicalities of developing a simple gateway using a functional programming language for low-end devices, such as a Raspberry Pi and an ESP32. We also want to understand the state of libraries needed for practical applications using this style of programming and gain some insight into the challenges of developing a system such as this when the developer is unfamiliar with functional programming. Finally, we want to gain some insight into the performance of a system written in a functional programming language when compared with more conventional languages.

3. Materials and Methods

In this section, we describe the four software implementations, including key code from each system. All four systems we developed had the same high-level functionality of receiving messages from a LoRa-enabled node and forwarding them via MQTT onto the Internet.

3.1. Elixir System on Raspberry Pi

In this section, we briefly describe the main Elixir code we developed for this first system running on a Raspberry Pi. One of the key points to observe is its compactness and the use of recursion in processing messages as they arrive. The high-level architecture of the system is shown in Figure 2.
The key function of the system is serial_get() as follows. It acts as an inbox within the Elixir system for the USB port, which receives messages from the targeted USB connected to the LoRa transceiver. Upon receipt of a message, it makes use of the Tortoise package that acts as an MQTT client to deliver the message to the MQTT broker [19]. Most of the Elixir gateway behaviour is defined here. Of particular note is that the usage of pattern matching allows for very concise error handling.
  •   def serial_get() do
        receive do
          {:circuits_uart,pid,{:error,error}} ->
            IO.puts "An error occured: #{inspect error}"
          {:circuits_uart,pid,text} ->
           Tortoise.publish(MqttLora,
                          "#{@subscription}",text)
        end
        serial_get()
      end
The other important part of the code is the supervisor, which is initiated upon system startup. This initiates both the serial_get() module as well as the Tortoise client that communicates with the MQTT broker. The module is also responsible for handling fatal system faults in a graceful manner.
  •   def init(args) do
      {:ok, mqtt_pid} =Tortoise.Connection.start_link(
          client_id: MqttLora,
         handler: {Tortoise.Handler.Logger, []},
          server: {Tortoise.Transport.Tcp,
             host: ’test.mosquitto.org’ , port: 1883 }
        )
        alias Mqtt.Handler, as: Start
        {:ok,uart_pid} = Circuits.UART.start_link
        {:ok,args}
        port = Start.serial_port
        if Circuits.UART.open(uart_pid, port, speed: 115200,
                     active: true) ==:ok do
          IO.puts "#{port}: Opened successfully"
          Circuits.UART.configure(uart_pid, framing:
           {Circuits.UART.Framing.Line, separator: "\r\n"})
          Circuits.UART.configure(uart_pid, id: :pid)
          IO.puts "Serial configuration completed"
          # publish a message on the broker
          serial_get()
        else
          IO.puts "#{port}: IO ERROR"
        end
    end

3.2. C++ System on Raspberry Pi

In order to compare the performance of the system with a conventional programming language, we developed a C++ version of the system that carried out the same function as the Elixir-based system on the Raspberry Pi.
We now discuss the main code sections. The code begins by defining necessary libraries, the addresses of the MQTT server and client before establishing a link between them.
  • #include <iostream>
    #include <fcntl.h>
    #include <termios.h>
    #include <unistd.h>
    #include <cstring>
    #include <mqtt/async_client.h>
     
    const std::string
           SERVER_ADDRESS("tcp://test.mosquitto.org:1883");
    const std::string CLIENT_ID("lora_mqtt_cpp_client");
    const std::string TOPIC("loraserial/");
    mqtt::async_client cli(SERVER_ADDRESS,CLIENT_ID);
The next section defines the serial interface that connects the Arduino Yun with the LoRa shield to the Raspberry Pi. This mostly comprises setting up flags and attributes to enable data to be transferred.
  • using namespace std;
    int OpenSerial(const char* port)
    {
        int fd = open(port, O_RDWR | O_NOCTTY | O_SYNC);
        if(fd < 0)
        {
            std::cerr << "Port Error: " << strerror(errno)
                                                     << std::endl;
            return -1;
        }
        struct termios tty;
        if(tcgetattr(fd,&tty) != 0)
        {
            std::cerr << "Attribute Error: " << strerror(errno)
                                                        << std::endl;
            close(fd);
            return -1;
        }
        cfsetospeed(&tty,B115200);
        cfsetispeed(&tty,B115200);
        tty.c_cflag &= ~PARENB;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CSIZE;
        tty.c_cflag |= CS8;
     
        tty.c_iflag &= ~(IXON | IXOFF | IXANY);
        tty.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
        tty.c_oflag &= ~OPOST;
        tcsetattr(fd,TCSANOW,&tty);
        return fd;
    }
We now come to the two main sections of code. The first is a routine that publishes data to the MQTT client, and the second reads data from the input buffer and uses the first routine to publish it.
  • void PublishMqtt(const std::string& msg)
    {
        mqtt::message_ptr pubmsg = mqtt::make_message(TOPIC,msg);
        pubmsg->set_qos(0);
        cli.publish(pubmsg);
    }
    void ReadPub(int fd)
    {
        static std::string buff;
        char buf[256];
        ssize_t n = read(fd,buf,sizeof(buf) −1);
        if(n>0)
        {
            buf[n] = ’\0’;
            std::string msg(buf);
            msg.erase(0,msg.find_first_not_of(" \t\r\n"));
            msg.erase(msg.find_last_not_of(" \t\r\n") + 1);
            if(!msg.empty())
            {
                std::cout << msg << std::endl;
                PublishMqtt(msg);
            }
     
        }
     
    }
Finally, we have the main code that initiates the system and continually reads data from the LoRa network and publishes it to MQTT using the previously defined routine.
  • int main()
    {
     
        mqtt::connect_options connOpts;
        connOpts.set_clean_session(true);
        mqtt::connect_options connectionOptions = connOpts;
        cli.connect(connectionOptions)->wait();
        const char* port = "/dev/ttyACM0";
        int fd = OpenSerial(port);
        while(true)
        {
            ReadPub(fd);
            usleep(1000);
        }
        return 0;
    }

3.3. Elixir System on ESP32

In order to experiment with functional programming on low-end devices such as the ESP32, we rewrote the Elixir system. The most substantial difference was that rather than using a serial interface, we needed to use an SPI interface to connect the ESP32 to the transceiver. This led us to restructure the system to comprise two components: a client and an MQTT gateway. The client is responsible for receiving data from the LoRa transceiver and forwarding it to the MQTT gateway. The gateway then communicates via WiFi to the Internet.
Code for the MQTT gateway is shown below. This code is initiated upon startup of the system. The system makes use of a callback handler so that the MQTT gateway does not attempt to transmit data until it is fully initialised.
  •   def start_mqtt() do
        config = %{url: AppConfig.mqtt_endpoint(),
                  connected_handler: &handle_connected/1}
        {:ok, mQTT} = :mqtt_client.start(config)
        :io.format(~c"MQTT started.~n")
        :erlang.register(:mqtt_instance, mQTT)
        :timer.sleep(:infinity)
      end
Once the MQTT gateway starts and the callback handler completes initialisation, it subscribes to the MQTT topic. Following that, it spawns the required processes that initialise the LoRa hardware.
  •   defp handle_subscribed(mQTT, topic) do
        :io.format(~c"Subscribed to ~p.~n", [topic])
        :erlang.spawn(fn -> Lora.start_receiver() end)
      end
Once the system is initialised, it waits for valid LoRa transmissions. Upon receipt of one, it forwards the message to the MQTT gateway with the appropriate label.
  • defp handle_receive(lora, packet, qoS) do
        :io.format(~c"Received Packet: ~p; QoS: ~p~n",
                           [packet, qoS])
        Mqtt.publish(Process.whereis(:mqtt_instance),
                          "lora_transceiver/elx", packet)
      end
Finally, it publishes it to the MQTT broker. This is performed via an MQTT client on a laptop to which the WiFi network is attached. This requires the MQTT client be initialised before the system is started. The line of code beginning with an underscore completes the transaction. No publishing receipts are kept. It is possible to process the response from the MQTT client, but in this case, we chose not to.
  • def publish(mQTT, topic, msg) do
      :io.format(~c"Publishing data on topic ~p~n", [topic])
        publishOptions = %{qos: :exactly_once}
        _ = :mqtt_client.publish(mQTT, topic, msg, publishOptions)
    end
The code that connects to the LoRa transceiver is shown below. Because we were unable to find an Elixir driver for the transceiver, we wrote this in Erlang, a language similar to Elixir. This code is mostly concerned with initiating and connecting via the SPI interface that connects the LoRa transceiver to the ESP32.
  • -define(DEVICE_NAME, lora_transceiver).
     
    -spec lora_config(Device :: sx127x | sx126x) -> map().
    lora_config(Device) ->
        #{
            spi => spi:open(spi_config(Device)),
            device_name => ?DEVICE_NAME,
            device => Device,
            irq => 26,
            busy => 0,
            reset => 14,
            frequency => freq_433mhz,
            tx_power => 17
        }.
     
    %% @private
    spi_config(Device) ->
        #{
            bus_config => #{
                miso_io_num => 19,
                mosi_io_num => 23,
                sclk_io_num => 18
            },
            device_config => #{
                ?DEVICE_NAME => #{
                    address_len_bits =>
                        case Device of
                            sx127x -> 8;
                            _ -> 0
                        end,
                    spi_clock_hz => 1000000,
                    mode => 0,
                    spi_cs_io_num => 33
                }
            }
        }.
Once the interface is connected, LoRa messages received via it need to be forwarded onto the MQTT gateway. This is achieved in the following code.
  •     try do
          case(:lora.broadcast(lora, payload)) do
            :ok ->
              :io.format(~c"Sent ~p~n", [payload])
     
            error ->
              :io.format(~c"Error sending: ~p~n", [error])
          end
        catch
          :exit, :timeout ->
            :io.format(~c"Timed out broadcasting ~p~n", [payload])
        end
     

3.4. C++ System on ESP32

The final system was developed in C++. The code below sets up the connection between the ESP32 and the transceiver. We note that this code is quite compact, mainly because of the availability of libraries that carry out this function. This has the advantage of simplicity, but it does hide the details of how the transceiver is connected.
  • #include <RH_RF95.h>
    #define RFM95_CS 15
    #define RFM95_RST 14
    #define RFM95_INT 26
    #define RF95_FREQ 865.20
    rf95.setFrequency(RF95_FREQ);
The next piece of code connects to the MQTT client. Again, because of the availability of libraries that carry out this function, the code is very concise.
  •    client.setServer("test.mosquitto.org", 1883);
       client.connect("lora_cpp");
Finally, the main logic for the system follows. We note that the code is quite concise.
  • void loop() {
     
    #if TRANSMITTER_MODE
      uint8_t data[50];
      sprintf((char*)data, "%i",random()) ;//
      rf95.send(data, sizeof(data));
      rf95.waitPacketSent();
      delay(DELAY);
     
    #else
      if (rf95.available()) {
        // Should be a message for us now
        uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
        uint8_t len = sizeof(buf);
        if (rf95.recv(buf, &len)) {
          publishData(buf);
        }
      }
    #endif
    }

4. Results

Our initial observation from this work is that functional programming excels where there is complex logic to be implemented. Although difficult to quantify, our impression is that in programs written using functional programming, the function of the system maps readily to the code and, once functional programming skills are learnt, are easy to program. However, the second observation is that the absence of built-in libraries that carry out low-level functions available in conventional languages adds considerably to the complexity of using functional programming on these devices. We now discuss each of the systems we developed in more detail.

4.1. Programming IoT Systems in Elixir on the Raspberry Pi

Functional programming languages such as Elixir are generally quite restrictive in how they allow applications to be structured. This does not mean that they are limited in what can be achieved, but they do impose trusted design patterns far more rigidly than is the case in other languages. Generally, this approach works well; however, where there is a limited range of supporting libraries, it does mean that it can be difficult to develop an application where support is poor. Our original intention was to use Erlang, an older functional programming language, but unfortunately, its support of USB serial interfaces was non-existent. We consequently chose Elixir, which has some supporting libraries for low-end transceiver devices. Nevertheless, libraries to support common IoT interfaces are an area that needs further development within the BEAM ecosystem and probably other functional programming languages.
We wrote the Raspberry Pi system as a generic application. The characteristics of a generic application when compared with other frameworks used in Elixir is that a generic application is not reliant on having a full installation of the Elixir development environment and allows more flexible start-up context control to the developer, allowing multiple methods of starting and greater control over environmental parameters but adding to the overall complexity of the system.
Once we decided on a framework, we were able to appreciate some of the strengths of this approach to developing systems. In particular, we found that the immutability of variables and flow of control defined solely by function calls imposed a design pattern that made development very straightforward when combined with the excellent pattern matching abilities of Elixir, allowing for an extremely concise software description of the desired system behaviour.

4.2. Programming IoT Systems in C++ on the Raspberry Pi

Developing this system was a straightforward process. Raspberry Pi are essentially fully featured computers operating a version of Linux (Raspbian) with all the associated libraries and capabilities. It is worth noting that the C++ code is less compact than the Elixir code. However, it is also worth noting that compact code does not mean it is necessarily more efficient in programming or performance terms.

4.3. Programming IoT Systems in Elixir on the ESP32

Programming this system was difficult, not because of the complexity of the programming, but because of the difficulty of finding suitable software. The first major challenge was finding a means of programming a micro-controller in a functional manner. We were fortunate to eventually find AtomVM, a virtual machine tailored towards low-end micro-controllers, compatible with a subset of the BEAM VM’s instruction, which made it a viable choice [7]. However, most developers who use this BEAM implementation use Erlang, rather than Elixir. Consequently, our system that used a mix of Elixir and Erlang was quite novel but meant we had many obstacles to overcome. We found documentation non-existent or limited. Error messages generated by the AtomVM were also limited in their usefulness. We also encountered problems with the AtomVM GPIO driver. However, it is worth noting these are not problems with Erlang and Elixir but with the AtomVM. Our experience here supports the observation that we have made previously that functional programming in this area requires far more mature supporting systems such as appropriate VMs and drivers.

4.4. Programming IoT Systems in C++ on the ESP32

Compared with programming with Elixir, this was much more straightforward. There are more well-developed libraries for C++ than for Elixir on the ESP32. We can make a few brief observations about the C++ code compared with the Elixir code. The main one is that because of the well-developed libraries for C++, the code is quite a lot shorter. The C++ system is 87 lines, whereas the Elixir system is 277. However, much of the code of the Elixir system carries out functions that are hidden in libraries in the C++ system. Because the processing required here is simple, the advantage of functional programming over C++ is not as apparent when considering only lines of code. However, the heart of the Elixir system, which embeds the logic of the system, is much more compact than that of the C++ system. Our observation is that more complex logic is expressed more compactly in functional programming languages than in conventional languages. However, it is also important to note that the compactness of the code tells us little about performance, maintainability or resource efficiency. These are matters for future research.

4.5. Performance of Raspberry Pi-Based Systems

We compared performance by seeing how well the two gateways (Elixir and C++) running on the Raspberry Pi performed in transferring data from the LoRa network to the Internet via MQTT. Traffic was generated by the LoRa node at a fixed rate at deterministic intervals. Therefore, for the offered traffic rate of 0.5 messages per second, a message was generated every two seconds. For an offered rate of 1 message per second, a message was generated every second and so on. Each experiment was run for ten minutes.
From the plots that follow, (Figure 6, Figure 7 and Figure 8), we see that the Raspberry Pi system performed very well for the traffic offered. There was no loss of messages up to four messages a second for both systems. The throughput matches the offered load, the mean interarrival time matches the time between messages and the loss rate is negligible. Latency also appears to be negligible.

4.6. Performance of ESP32-Based Systems

In this section, we describe our experiments and examine how well the two gateways (Elixir and C++) running on the less powerful ESP32 performed in transferring data from the LoRa network to the Internet via MQTT. We generated traffic in the same way as for the Raspberry Pi system.
The results are shown in Figure 9. In this plot, the traffic successfully delivered onto MQTT is plotted against the offered traffic. If 100% of traffic is successfully delivered, the plot would comprise a straight line with a gradient of one. We note that both systems deliver less than 100% of traffic when presented with quite light loads. Both systems can deliver close to 100% of traffic when the message rate is 0.5 messages per second (one message every two seconds), but when the message rate is one message per second, the Elixir system can deliver approximately only 70% of offered traffic. The C++ system loses some messages but far fewer than the Elixir system. By contrast, the Elixir system appears to come close to collapse once the offered load exceeds two messages a second.
The second performance metric is of mean interarrival time of packets delivered via MQTT. The results are shown in Figure 10. Once again, we see that the interarrival time decreases gracefully for the C++ system but much less so for the Elixir system once the offered traffic exceeds 1.5 messages per second.
We can estimate the latency of messages from the plot of the mean interarrival time. For the offered traffic of 0.5 messages per second, the latency of the two systems is similar and approximately 0.1 to 0.2 s, but as the offered traffic increases, we can see that the latency of the Elixir system increases substantially more than the C++ system. For offered traffic of 1 message per second, the C++ system message latency is approximately 0.1 s, but for the Elixir system, it is approximately 0.5 s. Once the offered traffic exceeds 1.5 messages per second, the latency of the Elixir system increases substantially as messages are lost.
The final performance metric is the mean loss rate of packets. The results are shown in Figure 11. Both systems lose messages, but once again, the Elixir-based system loses far more than the C++ system at the same offered rate, and the behaviour of the Elixir system is much more erratic than the C++ system.
Clearly, despite the advantages of functional programming in systems development, the performance of the Elixir system is greatly inferior to that of the C++ system. This could perhaps be improved with the further development of micro-controller-oriented BEAM implementations. However, until such improved implementations are available, when determining the capacity of such systems, it is important to take the lesser performance of the Elixir system into account.

4.7. Performance Comparison of Elixir Systems on Raspberry Pi and ESP32

In this section, we extract some of the data from the previous sub-sections to provide a comparison of Elixir on both platforms. An area of future research will be to investigate the performance of other platforms and virtual machines. This section gives us a direct comparison between both platforms that can be used as a baseline for future investigations.
The plots below (Figure 12, Figure 13 and Figure 14) show the much better performance of the Raspberry Pi compared with the ESP32.

5. Discussion

The emerging model of IoT networking is one of a gateway that links a specialised IoT network such as LoRa to an Internet-based broker such as MQTT. In the systems described in this paper, we developed a LoRa-MQTT gateway using the functional programming language Elixir and C++ on low-level embedded devices (Raspberry Pi and ESP32). We found that although functional programming has quite a steep learning curve, code developed using functional programming is compact, easily understood and logically structured because of its excellent pattern-matching support. We believe these characteristics are likely to make it more robust than code developed using conventional languages.
Despite our generally positive experience of Elixir as a language for IoT development, we nevertheless believe there is additional work needed in a number of areas before it can be more widely used, especially where hardware interaction is important.
The first is in the performance of BEAM implementations. With the implementations we used, much of the advantage of functional programming in terms of simplicity of code was lost because of the absence of libraries that supported basic functions. On the low-end ESP32, we observed much poorer performance on the Elixir-based system than on the C++ system.
The second is appropriate design patterns. Functional programming languages impose particular design patterns far more strictly than conventional programming languages. Appropriate design patterns for the IoT need to be identified and developed.
The third is in supporting infrastructure. We found the supporting libraries, VMs and frameworks for developing systems in the IoT space to be limited. In particular, we found connecting to other system components using standard hardware interfaces often problematic. We found that the lack of built-in libraries increased the complexity of the ESP32-based system compared with the Raspberry Pi system. The SPI and USB serial interfaces both caused us difficulties on the ESP32 and Raspberry Pi, respectively. Further work in developing these functions or adapting functions developed for higher-end systems is necessary before it can be more widely adopted in the IoT.
The fourth area of research needed is in gaining a better understanding of the performance and trade-offs in using this approach to system development. In particular, experiments need to be developed and carried out that further explore the errors, robustness and advantages and disadvantages of the different platforms that can be used in developing systems of this kind. In Section 4.7, we looked at two platforms that support this approach to development, which are at extremes of capability. Are there intermediate platforms that are lower cost than the Raspberry Pi but able to perform better than the ESP32? We have anecdotal evidence from our development of two systems that the approach seems to work well. Can we carry out experiments that are able to give more concrete evidence of the strengths and weaknesses of the approach? Finally, are there other performance metrics that are worth exploring apart from throughput, interarrival time and loss? Perhaps metrics such as memory usage, CPU cycles and energy consumption may give us some insights into the performance of these systems and the effectiveness of the approach.
Finally, a detailed statistical analysis of data obtained from experiments using this approach to ensure its reproducibility will be needed.

6. Conclusions

In this paper, we described the development of LoRa-MQTT gateways using functional programming on low-end devices and compared it with C++ systems with the same functionality. Although our results suggest Elixir is well suited as a development language for the IoT, we found that the performance of Elixir-based systems is substantially poorer on the ESP32 than the same system written in C++, and that libraries to support basic functions, particularly hardware support, are very limited and primitive in the Elixir and Erlang ecosystem.
Because our work suggests functional programming has much to offer in the development of IoT systems, we plan to contribute to addressing some of the current limitations of functional programming on low-end IoT devices. Such future work will include the development or adaptation of libraries for common design patterns found in the IoT. These will include interfacing lower-end communication devices and higher-level protocol support. We are also interested in developing design patterns for functional programming for common development scenarios within the IoT.
We believe this paper has added value to the area of IoT development by providing some insight into the strengths and weaknesses of using functional programming for this area as well as some understanding of the performance constraints on running such systems on IoT devices. Our results suggest that functional programming has considerable potential for the IoT. Nevertheless, for that potential to be realised much work is needed to improve the performance of the libraries and virtual machines that are used.

Author Contributions

Conceptualization, P.B. and P.W.; methodology, P.B. and P.W.; software, P.W.; validation, P.B. and P.W.; formal analysis, P.B. and P.W.; investigation, P.B. and P.W.; resources, P.B.; data curation, P.B. and P.W.; writing—original draft preparation, P.B. and P.W.; writing—review and editing, P.B. and P.W.; project administration, P.B.; funding acquisition, P.B. All authors have read and agreed to the published version of the manuscript.

Funding

This research received no external funding.

Data Availability Statement

The raw data supporting the conclusions of this article will be made available by the authors upon request.

Acknowledgments

The authors would like to express their gratitude to Fred Dushin for his work on AtomVM and his very generous help in getting our software to run on it and note with sadness his recent death. We thank the rest of the AtomVM community for their helpful contributions.

Conflicts of Interest

The authors declare no conflicts of interest.

Abbreviations

The following abbreviations are used in this manuscript:
BEAMBjörn’s Erlang Abstract Machine
C++C plus plus
GPIOGeneral Purpose Input/Output
IoTInternet of Things
MQTTMQ telemetry transport
SPISerial Peripheral Interface
USBUniversal Serial Bus
VMVirtual Machine

References

  1. Armstrong, J. A History of Erlang. In Proceedings of the Third ACM SIGPLAN Conference on History of Programming Languages, New York, NY, USA, 9–10 June 2007; HOPL III. pp. 6-1–6-26. [Google Scholar] [CrossRef]
  2. Schrickte, L.; Montez, C.; Oliveira, R.; Pinto, A. Design and implementation of a 6LoWPAN gateway for wireless sensor networks integration with the internet of things. Int. J. Embed. Syst. 2016, 8, 380. [Google Scholar] [CrossRef]
  3. LoRa Alliance. What Is the LoRaWAN Specification; LoRa Alliance: Fremont, CA, USA, 2022. [Google Scholar]
  4. Vivek, G.; Sunil, M. Enabling IoT services using WIFI–ZigBee gateway for a home automation system. In Proceedings of the 2015 IEEE International Conference on Research in Computational Intelligence and Communication Networks (ICRCICN), Kolkata, India, 20–22 November 2015; pp. 77–80. [Google Scholar] [CrossRef]
  5. Li, Y.; Fujita, S. Design of Elixir-Based Edge Server for Responsive IoT Applications. In Proceedings of the 2022 Tenth International Symposium on Computing and Networking Workshops (CANDARW), Himeji, Japan, 21–22 November 2022; pp. 185–191. [Google Scholar] [CrossRef]
  6. Thomas, D. Programming Elixir 1.6: Functional, Concurrent, Pragmatic, Concurrent, Fun; Pragmatic Bookshelf: Raleigh, NC, USA, 2018. [Google Scholar]
  7. AtomVM The Erlang Virtual Machine for IoT Devices. Available online: https://www.atomvm.net/ (accessed on 4 June 2024).
  8. Raspberry Pi Data Sheet. Available online: https://www.farnell.com/datasheets/2163186.pdf (accessed on 22 July 2024).
  9. Dragino LoRa Shield Data Sheet. Available online: https://www.dragino.com/downloads/downloads/LoraShield/Datasheet_LoraShield.pdf (accessed on 22 July 2024).
  10. Arduino Yun Data Sheet. Available online: https://www.arduino.cc/en/uploads/Main/YUN-V04(20150114).pdf (accessed on 22 July 2024).
  11. ESP32 Data Sheet. Available online: https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf (accessed on 22 July 2024).
  12. InAir9B Data Sheet. Available online: https://modtronix.com/mx-m/inair/inair9b_r1.pdf (accessed on 22 July 2024).
  13. Armstrong, J. Programming Erlang: Software for a Concurrent World; Pragmatic Bookshelf: Dallas, TX, USA, 2007; ISBN 193435600X. [Google Scholar]
  14. Branch, P.; Cricenti, T. A LoRa Based Wireless Relay Network for Actuator Data. In Proceedings of the 2020 International Conference on Information Networking (ICOIN), Barcelona, Spain, 7–10 January 2020; pp. 190–195. [Google Scholar]
  15. Branch, P.; Cricenti, T. A LoRa Relay Based System for Detonating Explosives in Underground Mines. In Proceedings of the 2020 IEEE International Conference on Industrial Technology (ICIT), Buenos Aires, Argentina, 26–28 February 2020; pp. 259–264. [Google Scholar]
  16. Branch, P.; Li, B.; Zhao, K. A LoRa-Based Linear Sensor Network for Location Data in Underground Mining. Telecom 2020, 1, 68–79. [Google Scholar] [CrossRef]
  17. Lundell, D.; Hedberg, A.; Nyberg, C.; Fitzgerald, E. A Routing Protocol for LoRa Mesh Networks. In Proceedings of the 2018 IEEE 19th International Symposium on “A World of Wireless, Mobile and Multimedia Networks” (WoWMoM), Chania, Greece, 12–15 June 2018; pp. 14–19. [Google Scholar] [CrossRef]
  18. Lee, H.C.; Ke, K.H. Monitoring of Large-Area IoT Sensors Using a LoRa Wireless Mesh Network System: Design and Evaluation. IEEE Trans. Instrum. Meas. 2018, 67, 2177–2187. [Google Scholar] [CrossRef]
  19. Hunkeler, U.; Truong, H.L.; Stanford-Clark, A. MQTT-S—A publish/subscribe protocol for Wireless Sensor Networks. In Proceedings of the 2008 3rd International Conference on Communication Systems Software and Middleware and Workshops (COMSWARE ’08), Bangalore, India, 6–10 January 2008; pp. 791–798. [Google Scholar] [CrossRef]
  20. Adelantado, F.; Vilajosana, X.; Tuset-Peiro, P.; Martinez, B.; Melia-Segui, J.; Watteyne, T. Understanding the Limits of LoRaWAN. IEEE Commun. Mag. 2017, 55, 34–40. [Google Scholar] [CrossRef]
  21. Phan, L.A.; Kim, T. Breaking Down the Compatibility Problem in Smart Homes: A Dynamically Updatable Gateway Platform. Sensors 2020, 20, 2783. [Google Scholar] [CrossRef] [PubMed]
  22. Jiang, Z.; Chang, Y.; Liu, X. Design of software-defined gateway for industrial interconnection. J. Ind. Inf. Integr. 2020, 18, 100130. [Google Scholar] [CrossRef]
  23. Mahnke, W.; Leitner, S.; Damm, M. OPC Unified Architecture; Springer: Berlin/Heidelberg, Germany, 2009. [Google Scholar]
  24. Haenisch, T. A case study on using functional programming for internet of things applications. Athens J. Technol. Eng. 2016, 3, 29–38. [Google Scholar] [CrossRef]
  25. Li, Y.; Fujita, S. A Synergistic Elixir-EDA-MQTT Framework for Advanced Smart Transportation Systems. Future Internet 2024, 16, 81. [Google Scholar] [CrossRef]
  26. Hasselbring, W.; Wojcieszak, M.; Dustdar, S. Control Flow Versus Data Flow in Distributed Systems Integration: Revival of Flow-Based Programming for the Industrial Internet of Things. IEEE Internet Comput. 2021, 25, 5–12. [Google Scholar] [CrossRef]
  27. Pereira, R.; Couto, M.; Ribeiro, F.; Rua, R.; Cunha, J.; Fernandes, J.P.; Saraiva, J. Ranking programming languages by energy efficiency. Sci. Comput. Program. 2021, 205, 102609. [Google Scholar] [CrossRef]
Figure 1. Internet of Things gateway architecture.
Figure 1. Internet of Things gateway architecture.
Electronics 13 03427 g001
Figure 2. LoRa-MQTT gateway on a Raspberry Pi.
Figure 2. LoRa-MQTT gateway on a Raspberry Pi.
Electronics 13 03427 g002
Figure 3. LoRa-MQTT gateway on an ESP32.
Figure 3. LoRa-MQTT gateway on an ESP32.
Electronics 13 03427 g003
Figure 4. Raspberry Pi-based gateway.
Figure 4. Raspberry Pi-based gateway.
Electronics 13 03427 g004
Figure 5. ESP32-based gateway.
Figure 5. ESP32-based gateway.
Electronics 13 03427 g005
Figure 6. Throughput on C++ and Elixir systems on the Raspberry Pi.
Figure 6. Throughput on C++ and Elixir systems on the Raspberry Pi.
Electronics 13 03427 g006
Figure 7. Mean interarrival times on C++ and Elixir systems on the Raspberry Pi.
Figure 7. Mean interarrival times on C++ and Elixir systems on the Raspberry Pi.
Electronics 13 03427 g007
Figure 8. Loss rates on C++ and Elixir systems on the Raspberry Pi.
Figure 8. Loss rates on C++ and Elixir systems on the Raspberry Pi.
Electronics 13 03427 g008
Figure 9. Throughput on C++ and Elixir systems on the ESP32.
Figure 9. Throughput on C++ and Elixir systems on the ESP32.
Electronics 13 03427 g009
Figure 10. Mean interarrival times on C++ and Elixir systems on the ESP32.
Figure 10. Mean interarrival times on C++ and Elixir systems on the ESP32.
Electronics 13 03427 g010
Figure 11. Loss rates on C++ and Elixir systems on the ESP32.
Figure 11. Loss rates on C++ and Elixir systems on the ESP32.
Electronics 13 03427 g011
Figure 12. Elixir system throughput on Raspberry Pi and ESP32.
Figure 12. Elixir system throughput on Raspberry Pi and ESP32.
Electronics 13 03427 g012
Figure 13. Elixir system mean interarrival time on Raspberry Pi and ESP32.
Figure 13. Elixir system mean interarrival time on Raspberry Pi and ESP32.
Electronics 13 03427 g013
Figure 14. Elixir system loss rates on Raspberry Pi and ESP32.
Figure 14. Elixir system loss rates on Raspberry Pi and ESP32.
Electronics 13 03427 g014
Table 1. Comparison of platforms.
Table 1. Comparison of platforms.
FeatureRaspberry PiESP32
EthernetYesNo
WiFiYes (USB dongle)Yes
IP StackYesYes
Cost (AUD)58.918.49
Power Consumption (active)1.2 W500 mW
Memory (RAM)1 GB520 kB
Processor Speed (MHz)900160
Disclaimer/Publisher’s Note: The statements, opinions and data contained in all publications are solely those of the individual author(s) and contributor(s) and not of MDPI and/or the editor(s). MDPI and/or the editor(s) disclaim responsibility for any injury to people or property resulting from any ideas, methods, instructions or products referred to in the content.

Share and Cite

MDPI and ACS Style

Branch, P.; Weinstock, P. Functional Programming for the Internet of Things: A Comparative Study of Implementation of a LoRa-MQTT Gateway Written in Elixir and C++. Electronics 2024, 13, 3427. https://doi.org/10.3390/electronics13173427

AMA Style

Branch P, Weinstock P. Functional Programming for the Internet of Things: A Comparative Study of Implementation of a LoRa-MQTT Gateway Written in Elixir and C++. Electronics. 2024; 13(17):3427. https://doi.org/10.3390/electronics13173427

Chicago/Turabian Style

Branch, Philip, and Phillip Weinstock. 2024. "Functional Programming for the Internet of Things: A Comparative Study of Implementation of a LoRa-MQTT Gateway Written in Elixir and C++" Electronics 13, no. 17: 3427. https://doi.org/10.3390/electronics13173427

Note that from the first issue of 2016, this journal uses article numbers instead of page numbers. See further details here.

Article Metrics

Back to TopTop