O Elixir da IoT

Geovane Fedrecheski

geonnave@

Hello

IoT/Swarm Group

IoT?

The Internet

The Internet of Things

IoT applications

Smart infrastructure

Healthcare

Logistics

Social applications

 

Industry case

T

T

T

H

H

A

Demo

mix firmware
mix firmware.burn
defmodule Blinky do
  @on_duration  200 # ms
  @off_duration 200 # ms

  require Logger

  def start(_type, _args) do
    {:ok, gpio_11_pid} = Gpio.start_link(11, :output)
    spawn fn -> blink_led_forever(gpio_11_pid) end
    {:ok, self}
  end


  defp blink_led_forever(gpio_11_pid) do
    Gpio.write(gpio_11_pid, 1)
    Logger.info "gpio_11_pid ON"
    :timer.sleep @on_duration

    Gpio.write(gpio_11_pid, 0)
    Logger.info "gpio_11_pid OFF"
    :timer.sleep @off_duration

    blink_led_forever(gpio_11_pid)
  end
end

WTF

Elixir?

-Java?? Look at Haskell, or Erlang

So what's interesting about

this Erlang thing?

What even an "Erlang" is?

Erlang

  • Concurrency
  • Soft real-time
  • Distribution
  • Continuous Operation
  • Fault Tolerance

~1980

  • Concurrency
  • Soft real-time
  • Distribution
  • Continuous Operation
  • Fault Tolerance

Functional
Processes (instead of objects)

Cases

Processes

Concurreny

Fault tolerance

Data structures

Consistent libs

Modern tooling

I loved everything that Erlang had

but I hated everything that it didn't had

- José Valim

José Valim

Core committer

Unsatisfied with thread-based concurrency

~2010

=>

Elixir

~2011

Elixir

Goals

  • Extensibility

  • Productivity

  • Compatibility

Elixir

Erlang

The VM

Concurreny

Fault tolerance

Data structures

Consistent libs

Modern tooling

defmodule SumServer do
  use GenServer
  
  def start do
    GenServer.start(__MODULE__, nil)
  end

  def sum(server, a, b) do
    GenServer.call(server, {:sum, a, b})
  end
  
  def handle_call({:sum, a, b}, _from, state) do
    {:reply, a + b, state}
  end
end
-module(sum_server).

-behaviour(gen_server).
-export([
  start/0, sum/3,
  init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
  code_change/3
]).

start() -> gen_server:start(?MODULE, [], []).

sum(Server, A, B) -> gen_server:call(Server, {sum, A, B}).

init(_) -> {ok, undefined}.
handle_call({sum, A, B}, _From, State) -> {reply, A + B, State};
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

Pattern Matching

> {method, path} = {:get, "temp"}
{:get, "temp"}
> method
:get
> path
"temp"
request = {:get, "temp"}



case request do
  {:post, path, payload} ->
    send_resp 200
  {:get, path} ->
    send_resp 200, "{'value':17.5, 'unit': 'celsius'}"
end
defmodule Math do
  def fib(0), do: 0
  def fib(1), do: 1
  def fib(n), do: fib(n-1) + fib(n-2)
end

Math.fib(8) # => 21

Processes

spawn

A lightweight thread of execution

(The key Erlang/Elixir abstraction)

other_function() |> new_function() |> baz() |> bar() |> foo()
foo(bar(baz(new_function(other_function()))))

Pipe Operator

Processes are key

Performance

Concurrent applications

IoT =~

Embedded webservices

O Elixir da IoT

Raspberry Pi

Beaglebone Black

defmodule Blinky do
  @on_duration  200 # ms
  @off_duration 200 # ms

  require Logger

  def start(_type, _args) do
    {:ok, gpio_11_pid} = Gpio.start_link(11, :output)
    spawn fn -> blink_led_forever(gpio_11_pid) end
    {:ok, self}
  end


  defp blink_led_forever(gpio_11_pid) do
    Gpio.write(gpio_11_pid, 1)
    Logger.info "gpio_11_pid ON"
    :timer.sleep @on_duration

    Gpio.write(gpio_11_pid, 0)
    Logger.info "gpio_11_pid OFF"
    :timer.sleep @off_duration

    blink_led_forever(gpio_11_pid)
  end
end
wget 'http://director.downloads.raspberrypi.org/
raspbian_lite/images/raspbian_lite-2016-09-28/
2016-09-23-raspbian-jessie-lite.zip'

unzip 2016-09-23-raspbian-jessie-lite.zip
sudo dd \
 if/2016-09-23-raspbian-jessie-lite.img \
 of/dev/sdc
sudo vi /etc/apt/sources.list
# deb http://packages.erlang-solutions.com/debian wheezy contrib

wget http://packages.erlang-solutions.com/debian/erlang_solutions.asc
sudo apt-key add erlang_solutions.asc

sudo apt-get update
sudo apt-get install git erlang

git clone https://github.com/elixir-lang/elixir.git
cd elixir
git checkout tags/v1.3.4
make clean test

# waaaaaaaaaaaaaait a LOT (aprox. 1h)

export PATH="$PATH:/path/to/elixir/bin"

SSH

defmodule Broker.AccessControl do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def authorize(request) do
    GenServer.call __MODULE__, {:authorize, request}
  end

  def handle_call({:authorize, request}, _from, _current) do
    ans = Broker.AccessControl.PDP.authorize(request)
    {:reply, ans, ans}
  end
end

Compile on board takes time...

host$ ssh pi@192.168.0.1

pi$ mix nerves.new hello_nerves --target rpi
pi$ cd hello_nerves
pi$ mix deps.get
pi$ mix compile
# takes TIME...

pi$ iex -S mix

But..

  • Repeat the process for each board?

    • Maybe write a script

  • Configuration/Compilation Takes time..

  • What if want to use another board? (will need a different setup)

Nerves

Craft and deploy bulletproof embedded software in Elixir

Code

Package

Run

Prepare

mix nerves.new hello_nerves --target rpi3
cd hello_nerves
mix deps.get
mix compile
(write your project in Elixir)
mix firmware
mix firmware.burn
NERVES_TARGET=bbb mix compile

Based on

Buildroot is a simple, efficient and easy-to-use tool to generate embedded Linux systems through cross-compilation.

Code

Package

Run

Prepare

VM

ARM

Toolchain

=

+

+

|>

{

,

}

=

FOTA (Firmware Over the Air) Upgrades!

$ http POST "192.168.0.121:8988/firmware" \
content-type:application/x-firmware \
x-reboot:true < firmware.fw

Run

How does it compare?

Image sizes

Nerves

Raspbian

~300MB

~20MB

Boot time

Nerves

Raspbian

~30 sec.

~3 sec.

Multiple Boards

Nerves

Raspbian

All drivers included

Nerves

Raspbian

The VM

Concurreny

Fault tolerance

Data structures

Consistent libs

Modern tooling

+

Small images

Multiple boards

Quick iteration

Cases

For more Information

Obrigado

IoT Architectures and Implementations

Functionaλ Programming

f(g(x), h(...))

Steve Vinoski, IEEE Computing Society 2009

"... ironically, the resurgence of interest in FP languages owes a lot to Java’s popularity."

"Another reason [...] is the move toward multicore architectures."

"... developers look for the opportunity to
get more done with less."

Welcome to the Functional Web

The Funcional Web

request
|> route()
|> handle()
|> send_resp()
send_resp(handle(
  route(request)))

Functional Programming Languages

Lisp

Elm

Elixir

Unsatisfied with thread-based concurrency

Repetitive strain

injury

by Bruce A. Tate

A bit of History

Erlang

context

Created in the Telecommunication industry

...

~1980

Programming language

Virtual Machine - BEAM

Framework - OTP

-module(fact).
-export([fac/1]).

fac(0) -> 1;
fac(N) -> N * fac(N-1).

Elixir

~2011

Features

Anonymous Functions

> add = fn(a, b) -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
> add.(1, 2)
3
> Enum.map([17.9, 20.2, 18.4], fn(x) -> x * 2 end)
[35.8, 40.4, 36.8]

Can be passed as parameters

Processes

spawn

A lightweight thread of execution

(The key Erlang/Elixir abstraction)

How is this different from threads?

  • No shared memory

  • VM Processes

Do not share memory

Communicate via

Message Passing

message

VM Processes

Excuse me, have you ever heard of (VM) processes?

Erlang VM

Scheduler

Sched.

Sched.

Sched.

OS Processes

OS Threads

VM Processes

OS

pid = spawn(fn ->
  :timer.sleep(3000)
  IO.puts "hi"
end)

fn ..

spawn

self()

sleep
puts

A lightweight thread of execution

Messages

pid = spawn(...)
send(pid, "hello")
receive do
  msg ->
    IO.puts msg
end

fn ..

spawn

self()

receive
send
Node.spawn(
  :"app@computer-2",
  fn -> Hello.world end)
send
receive
app@computer-1
app@computer-2

Distributed

Same syntax

We have:

  • Isolated Processes

  • Message passing

  • Distribution

We want:

  • Fault Tolerance

dangling processes!

spawn
spawn

call the supervisor

supervisor

worker

worker

worker

worker

Supervision Tree

import Supervisor.Spec

children = [
  worker(Cache, []),
  supervisor(DatabaseSupervisor, []),
  supervisor(TCPSupervisor, [4040])
]

Supervisor.start_link(children, strategy: :one_for_one)

Applications

your app

http
server

logger

app@computer-1

Distributed Applications

app@computer-2

Fault Tolerance

Open Telecom Platform

  • Supervisor

  • Application

  • GenServer

  • GenEvent

OOP

Objects

Elixir

Processes

Production-ready?

Main challenges

  • Resource constrained
  • Power efficiency
  • Communication unreliability
  • Context sensitivity
  • Remote management
  • Concurrency

Popular languages for IoT

  • C
  • C++
  • Java
  • Javascript
  • Python

Making the IoT
Functional with Elixir:
Four scenarios

  • Easy CoAP binary parser
     

  • Fault-tolerant microservices

  • Elixir and Java load test
     

  • Automatic deployment of Elixir images

CoAP

  • Constrained Application Protocol

  • kind of a "smaller HTTP, for IoT"

request

response

CoAP - Codes

(binary header)

0.01 (GET) (Request)

0.02 (POST) (Request)

2.05 (Content) (Response)

4.04 (Not Found) (Response)

000.00001

000.00010

010.00101

100.00100

CoAP

parser

1

0

0

0.01

123

01000000 00000001 0000000001111011 11111111

255

64 1 0 123 255

(empty)

(empty)

(empty)

%Message(type: :con,
         code: :get,
         msg_id: 123,
         options: [])
int coap_parseHeader(coap_header_t *hdr, 
                     const uint8_t *buf,
                     size_t buflen)
{
    if (buflen < 4)
        return COAP_ERR_HEADER_TOO_SHORT;
    hdr->ver = (buf[0] & 0xC0) >> 6;
    if (hdr->ver != 1)
        return COAP_ERR_VERSION_NOT_1;
    hdr->t = (buf[0] & 0x30) >> 4;
    hdr->tkl = buf[0] & 0x0F;
    hdr->code = buf[1];
    hdr->id[0] = buf[2];
    hdr->id[1] = buf[3];
    return 0;
}

https://github.com/1248/microcoap/blob/master/coap.c

C

def decode(<<1::2, type::2, token_len::4,
             code_class::3, code_detail::5,
             msg_id::16,
             token::binary-size(token_len),
             rest::binary>>) do
def decode(<<1::2, type::2, token_len::4,
             code_class::3, code_detail::5,
             msg_id::16,
             token::binary-size(token_len),
             rest::binary>>) do
  code = decode_code(code_class, code_detail)
  token = decode_token(token, token_len)
  {options, payload} =
    decode_options_and_payload(rest)

  %Message{
    version: 1,
    type: Registry.from(:types, type),
    code: code,
    msg_id: msg_id, token: token,
    options: options, payload: payload
  }
end
int coap_parseHeader(coap_header_t *hdr, 
                     const uint8_t *buf,
                     size_t buflen)
{
    if (buflen < 4)
        return COAP_ERR_HEADER_TOO_SHORT;
    hdr->ver = (buf[0] & 0xC0) >> 6;
    if (hdr->ver != 1)
        return COAP_ERR_VERSION_NOT_1;
    hdr->t = (buf[0] & 0x30) >> 4;
    hdr->tkl = buf[0] & 0x0F;
    hdr->code = buf[1];
    hdr->id[0] = buf[2];
    hdr->id[1] = buf[3];
    return 0;
}

Microservices

Source: http://martinfowler.com/articles/microservices.html

Microservices features

Source: http://martinfowler.com/articles/microservices.html

  • Services as components

  • Organized as business capabilites

  • Smart endpoints and dumb pipes

  • Decentralized Governance

  • Decentralized Data Management

  • Infrastructure Automation

  • Design for failure

  • Evolutionary Design

Microservices

"... my primary guideline would be:
don't even consider microservices unless you have a system that's too complex to manage as a monolith

Martin Fowler, MicroservicePremium, May 2015

The Swarm at the edge of the cloud

Elixir in times of microservices*

  • Processes as nanoservices

  • Processes packaged in Applications

  • Fault-tolerant processes

  • Process supervisors

  • Distributed Erlang

*http://blog.plataformatec.com.br/2015/06/elixir-in-times-of-microservices/

Elixir and Java

load test

Swarm Broker

Swarm Broker

Comparison

  • Lines of Code

  • CPU

  • Memory

  • Requests / sec.

Platform

  • Quad-core computer (x86)

  • 6GB of RAM

  • Java 1.8

  • Erlang 18.2

  • Elixir 1.2

  • Load test tool: Tsung 1.6

Lines of Code

Elixir

Java

551

176

Lines of Code

CPU

Memory

Requests / sec.

Obrigado

Gracias

Merci

Thanks

Appendix

:observer.start

(slide intentionally left blank)

(this is the appendix!)

Code

Compiler

Erlang

VM

(other OS processes)

OS

defmodule Math do
  # this is a comment.
  # below is a function
  def sum(a, b) do
    a + b
  end
end
# pattern matching is like method
# overloading on steroids!

defmodule Math do
  def zero?(0) do
    true
  end
  def zero?(x) when is_integer(x) do
    false
  end
end
{a, b, c} = {:hello, "world", 42} 
a # has value :hello
b # "world"
c # 42
b = :binary.encode_unsigned(258)
# b has value <<1, 2>>
<<x, 2>> = b
# x has value 1
i = :binary.decode_unsigned(<<x, 2>>)
# i has value 258

Elixir

Some Drips

my = self() #PID<0.141.0>

pid = spawn(fn -> 
  :timer.sleep(3000)
  send(my, {:get, "temp"})
end)

receive do # will block for 3 secs!
  {:post, path} ->
    IO.puts "was a post on path '#{path}'"
  {:get, path} ->
    IO.puts "was a get on path '#{path}'"
end

Pattern Matching

> x = 1
1
> 1 = x
1
> 2 = x
** (MatchError)

x = 1

The "match operator"

Assignment

match values

AND

bind values to variables

Code

Package

Run

Prepare

Elixir e a Internet das Coisas Funcional

By Geovane Fedrecheski

Elixir e a Internet das Coisas Funcional

  • 614