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.burndefmodule 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
endWTF
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'}"
enddefmodule 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
endCompile 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
endfn ..
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
}
endint 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 # 42b = :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 258Elixir
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}'"
endPattern 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
- 694