Difference between revisions of "Erlang"

From eLinux.org
Jump to: navigation, search
m (External Interfaces - Pi Camera)
 
(191 intermediate revisions by 3 users not shown)
Line 1: Line 1:
Erlang on RPi, starting with a fresh 2013-05-25-wheezy-raspbian.zip image.
+
Erlang on Raspberry Pi, starting with a fresh Raspberry Pi OS image (even Lite will do). Erlang takes about 20 minutes or more to compile (when you run "make"). <p>
  
<code><pre>
+
<pre>
 +
sudo apt-get update
 
sudo apt-get install wget
 
sudo apt-get install wget
 
sudo apt-get install libssl-dev
 
sudo apt-get install libssl-dev
 
sudo apt-get install ncurses-dev
 
sudo apt-get install ncurses-dev
wget http://www.erlang.org/download/otp_src_R16B01.tar.gz
+
wget https://github.com/erlang/otp/releases/download/OTP-26.2/otp_src_26.2.tar.gz
tar -xzvf otp_src_R16B01.tar.gz
+
tar -xzvf otp_src_26.2.tar.gz
cd otp_src_R16B01/
+
cd otp_src_26.2/
 
./configure
 
./configure
 
make
 
make
 
sudo make install
 
sudo make install
</pre></code>
 
You start the interactive shell with erl and quit with Ctrl-g q. Read about Erlang here [http://learnyousomeerlang.com]
 
== Introduction ==
 
 
On this page I will show how to use Erlang on RPi to put a sensor value from Gertboard on the web using an interface written in [[Forth]] and how to put a picture from Pi Camera on the web.
 
 
== A Web Server ==
 
Erlang is a perfect language to use for programming a web server, and that is exactly what Loïc Hoguin did. Cowboy is one of the most scalable web servers there is and it's also small so it fits nicely in a RPi. I was thinking of using it to display the kW load from my [[Forth | Atlast Forth]] interface to  [[RPi Gertboard | Gertboard]]. Here is how I did it, first install the cowboy web server and test run it:
 
<code><pre>
 
sudo apt-get install git
 
git clone git://github.com/extend/cowboy.git
 
cd cowboy
 
make
 
</pre></code>
 
To make it run you first have to tell it to accept HTTP:
 
<code><pre>
 
cd ~/cowboy/ebin
 
cp examples/hello_world/src/hello_world.app.src ./hello_world.app
 
erlc ../examples/hello_world/src/*.erl
 
 
cd ..
 
cd ..
cp examples/hello_world/start.sh .
+
rm otp_src_26.2.tar.gz
sudo ./start.sh
+
sudo rm -R otp_src_26.2/
</pre></code>
+
</pre>
Start a browser and point it to your RPi:8080, eg 192.168.0.178:8080
 
 
 
== External interfaces - Gertboard ==
 
Erlang processes communicate with the outside world using the same message passing mechanism as used between Erlang processes. This mechanism is used for communication with the host operating system and for interaction with programs written in other languages. If required for reasons of efficiency, a special version of this concept allows e.g. C programs to be directly linked into the Erlang runtime system. The easy way is good enough so just open a port to  [[Forth | Atlast Forth]]  in the file examples/hello_world/src/toppage_handler.erl. Replace toppage_handler.erl with the following:
 
<code><pre>
 
%% @doc Hello world handler.
 
-module(toppage_handler).
 
 
 
-export([init/3]).
 
-export([handle/2]).
 
-export([terminate/3]).
 
 
 
init(_Transport, Req, []) ->
 
        {ok, Req, undefined}.
 
  
handle(Req, State) ->
+
You start the interactive shell with erl and quit with Ctrl-g q. Read about Erlang at [http://learnyousomeerlang.com] and [http://spawnedshelter.com] and [http://ferd.ca/the-zen-of-erlang.html] There are also lots of languages running on top of the Erlang virtual machine [https://github.com/llaisdy/beam_languages].
  Port = open_port({spawn, "./priv/atlast -i./priv/kwh.atl"}, [{line,40}]),
 
  Port ! {self(), {command, "1 gertboard\n"}},
 
  Port ! {self(), {command, "w\n"}},
 
  receive
 
    {Port,{data,{eol,D}}} -> D
 
  after 20*1000 -> D = "timeout"
 
  end,
 
  Port ! {self(), {command, "0 gertboard\n"}},
 
  port_close(Port),
 
  {ok, Req2} = cowboy_req:reply(200, [], D, Req),
 
  {ok, Req2, State}.
 
  
terminate(_Reason, _Req, _State) ->
+
=== Graphics ===
        ok.
+
[[File:Ex11_graphics.jpg|thumb|right|Ex11_graphics]]
</pre></code>
+
There is a very nice graphics library written in Erlang. It makes a perfect match if your application is written in Erlang and you are using a small screen where you don't want a window manager. The screen in the image is an Adafruit 5" Touchscreen 800x480. Erlang ex11 is writing directly to the screen (X11 server or Mir). Experience soft realtime graphics. Get it here [https://github.com/skvamme/ex11]
cd to ebin and compile the file:
 
<code><pre>
 
cd ~/cowboy/ebin
 
erlc ../examples/hello_world/src/toppage_handler.erl
 
</pre></code>
 
Now, we need to put atlast in the ./priv directory, but first we have to get rid of the banner and the prompt.
 
  
In atlmain.c comment out  PR("ATLAST 1.2 (2007-10-07) This program is in the public domain.\n"); at line 55 and comment out      if (!fname) at 162,163,164 and 165. Save and run make again.
+
To help create new widgets there is a small program included in ex11 to convert DXF-files to erlang ex11 drawing primitives. Download or draw your own graphics in a CAD system and save as DXF. Convert the DXF file with dxf2erl:start("filename.dxf"). from the erlang console.
  
In directory cowboy, create a directory priv and copy atlast to it:
+
The DXF file can be created in LibreCAD [http://librecad.org/], QCAD [http://www.ribbonsoft.com/en/], FreeCAD [http://www.freecadweb.org], OpenSCAD [http://www.openscad.org] and other CAD systems. Entities with higher thickness will be drawn last. Closed polylines will be filled areas in ex11.
<code><pre>
 
cd ~/cowboy
 
mkdir priv
 
cp ../atlast-1.2/atlast ./priv
 
</pre></code>
 
Create a file kwh.atl and put it in the same priv directory with the word definitions for getting kW and W load values from Gertboard:
 
<code><pre>
 
( The first 25 getkwh is for detecting an edge, drop the result and find next edge. Convert the two integers to float and divide them. )
 
( 3.6 divided by the result. The result is load in kilowatts )
 
: kw 3.6 25 getkwh 2drop 25 getkwh float 2 roll float 2swap f/ f/ ." "kw=" f. cr ;
 
  
( The same thing done with integers only, here the result is in W )
+
=== Ports ===
: w 25 getkwh 2drop 25 getkwh 36 * swap 100 / / ." "w=" . cr ;
+
Erlang is using ports to communicate with all the different hardware that you are connecting to your Raspberry Pi. In the image to the right I am using an ex11 line graph widget to show the load on my mains meter in the basement. The Erlang Port I'm using is programmable in the language Forth, read more about it here [[Forth]]. The fact that it is programmable makes it possible to read multiple sensors and command multiple actuators simultaneously and it is even possible to programmatically re-program the port from a running Erlang application. It is also very handy to debug because you can start the port standalone and issue commands to it in its own console window.
</pre></code>
 
Ok, now we just have to call start.sh again and point the browser to 192.168.0.178:8080 or whatever ip address you have on your RPi. After a few seconds the W load should show up. I stop here, this is not the right forum to discuss how to make a web gui, lots of other sites can help with that. Have fun :)
 
  
Note: The suggested way to call the port directly from examples/hello_world/src/toppage_handler.erl is just to make it easy. In a real website with thousands of users you should open the port from a separate process and let it run and serve more than one web call.
+
=== Sockets ===
 +
You can also use TCP sockets to communicate with devices. A nice socket server is the pigpio daemon http://abyz.me.uk/rpi/pigpio/ . Easy to integrate in your Erlang application. More about this at https://github.com/skvamme/pigpio
  
== External Interfaces - Pi Camera ==
+
<pre>
If you have a PiCamera and wants to have a picture on a web page. The cool thing with this is that it's in real time, it is not a file that is served but a new picture is taken when you do the web call. Check that /opt/vc/bin/raspistill works on your system, then replace the code in examples/hello_world/src/toppage_handler.erl with the following: (Then follow the Gertboard example for compiling and running).  
+
-module(pigpio).
<code><pre>
+
-author(skvamme).
%% @doc Hello world handler.
+
-export([start/0,init/0,loop/1]).
-module(toppage_handler).
+
-define(PIGPIO_IP,"192.168.0.20").
 +
-define(PIGPIO_PORT,8888).
 +
-define(UINT,32/little).
  
-export([init/3]).
+
start() -> Pid = spawn_link(?MODULE,init,[]), {ok,Pid}.
-export([handle/2]).
 
-export([terminate/3]).
 
  
init(_Transport, Req, []) ->
+
init() ->
         {ok, Req, undefined}.
+
        io:format("New process: ~p~n", [?MODULE]),
 +
        inets:start(),
 +
        {ok,Socket} = gen_tcp:connect(?PIGPIO_IP, ?PIGPIO_PORT, [binary,{packet, 0}]),
 +
        ok = gen_tcp:send(Socket,c(setmode,25,0)),
 +
         ok = gen_tcp:send(Socket,c(getmode,25)),
 +
        %callback:make(self(),33554432), % Start a callback to monitor pin 25 on a dedicated socket
 +
        %timer:send_interval(10*1000, {timer}),
 +
        loop(Socket).
  
handle(Req, State) ->
+
loop(Socket) ->
  Port = open_port({spawn, "/opt/vc/bin/raspistill -t 2 -w 1024 -h 768 -o -"}, [binary]),
+
        receive
  Str = loop(Port,<<>>),
+
                % {timer} -> ok = gen_tcp:send(Socket,c(br1)),
  {ok, Req2} = cowboy_req:reply(200, [], Str, Req),
+
                %        ?MODULE:loop(Socket);
  {ok, Req2, State}.
+
                {tcp,Socket,Data} ->
 +
                        parse(Socket,Data),
 +
                        ?MODULE:loop(Socket);
 +
                {kwh,Sec} when Sec > 0 ->
 +
                        W = 3600000000 div Sec,
 +
                        io:format("W: ~p~n",[W]),
 +
                        ?MODULE:loop(Socket);
 +
                Any -> io:format("~p got unknown msg: ~p~n",[?MODULE, Any]),
 +
                        ?MODULE:loop(Socket)
 +
        end.
  
terminate(_Reason, _Req, _State) ->
+
%******************************************************************************
        ok.
+
% pigpio response
 +
%******************************************************************************
 +
parse(_Socket,<<>>) -> ok;
 +
parse(Socket,<<0:?UINT,P1:?UINT,P2:?UINT,0:?UINT,Rest/binary>>) ->
 +
        io:format("setmode pin ~p mode ~p~n",[P1,P2]),
 +
        parse(Socket,Rest);
 +
parse(Socket,<<1:?UINT,P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
 +
        io:format("getmode pin ~p mode ~p~n",[P1,P3]),
 +
        parse(Socket,Rest);
 +
parse(Socket,<<2:?UINT,P1:?UINT,P2:?UINT,0:?UINT,Rest/binary>>) ->
 +
        io:format("setpullupdown pin ~p pud ~p~n",[P1,P2]),
 +
        parse(Socket,Rest);
 +
parse(Socket,<<3:?UINT,_P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
 +
        io:format("read ~p~n",[P3]),
 +
        parse(Socket,Rest);
 +
parse(Socket,<<4:?UINT,P1:?UINT,P2:?UINT,0:?UINT,Rest/binary>>) ->
 +
        io:format("write pin ~p level ~p~n",[P1,P2]),
 +
        parse(Socket,Rest);
 +
parse(Socket,<<10:?UINT,_P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
 +
        io:format("readbits 0-31 ~.2B~n",[P3]),
 +
        parse(Socket,Rest);
 +
parse(Socket,<<17:?UINT,_P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
 +
        io:format("hver ~p~n",[P3]),
 +
        parse(Socket,Rest);
 +
parse(_Socket,Response) -> io:format("Error: ~w~n",[Response]).
  
loop(Port,Frame) ->
+
%******************************************************************************
  receive
+
% pigpio commands
    {Port, {data, Chunk}} ->  
+
%******************************************************************************
      Size = byte_size(Chunk) - 2,
+
%c(hver) -> <<17:?UINT,0:?UINT,0:?UINT,0:?UINT>>;
      case Chunk of
+
%c(br1) -> <<10:?UINT,0:?UINT,0:?UINT,0:?UINT>>;
        <<_:Size/binary,255,217>> -> Framestring = base64:encode_to_string(<<Frame/binary,Chunk/binary>>),
+
c(read,Gpio) -> <<3:?UINT,Gpio:?UINT,0:?UINT,0:?UINT>>;
                  "<html><img src = 'data:image/jpeg;base64," ++ Framestring ++ "'></html>";
+
c(getmode,Gpio) -> <<1:?UINT,Gpio:?UINT,0:?UINT,0:?UINT>>.
    _ -> loop(Port,<<Frame/binary,Chunk/binary>>)
+
c(setmode,Gpio,Mode) -> <<0:?UINT,Gpio:?UINT,Mode:?UINT,0:?UINT>>; % Input = 0, Output = 1
  end
+
c(write,Gpio,Level) -> <<4:?UINT,Gpio:?UINT,Level:?UINT,0:?UINT>>;
  after 20*1000 -> "<html>timeout</html>"
+
c(setpullupdown,Gpio,Pud) -> <<2:?UINT,Gpio:?UINT,Pud:?UINT,0:?UINT>>. % Off = 0, Down = 1, Up = 2
  end.
 
  
</pre></code>
+
</pre>
Note: The suggested way to call the port directly from examples/hello_world/src/toppage_handler.erl is just to make it easy. In a real website with thousands of users you should open the port from a separate process and let it run and serve more than one web call.
 
  
 
[[Category: RaspberryPi]]
 
[[Category: RaspberryPi]]

Latest revision as of 08:18, 13 December 2023

Erlang on Raspberry Pi, starting with a fresh Raspberry Pi OS image (even Lite will do). Erlang takes about 20 minutes or more to compile (when you run "make").

sudo apt-get update
sudo apt-get install wget
sudo apt-get install libssl-dev
sudo apt-get install ncurses-dev
wget https://github.com/erlang/otp/releases/download/OTP-26.2/otp_src_26.2.tar.gz
tar -xzvf otp_src_26.2.tar.gz
cd otp_src_26.2/
./configure
make
sudo make install
cd ..
rm otp_src_26.2.tar.gz
sudo rm -R otp_src_26.2/

You start the interactive shell with erl and quit with Ctrl-g q. Read about Erlang at [1] and [2] and [3] There are also lots of languages running on top of the Erlang virtual machine [4].

Graphics

Ex11_graphics

There is a very nice graphics library written in Erlang. It makes a perfect match if your application is written in Erlang and you are using a small screen where you don't want a window manager. The screen in the image is an Adafruit 5" Touchscreen 800x480. Erlang ex11 is writing directly to the screen (X11 server or Mir). Experience soft realtime graphics. Get it here [5]

To help create new widgets there is a small program included in ex11 to convert DXF-files to erlang ex11 drawing primitives. Download or draw your own graphics in a CAD system and save as DXF. Convert the DXF file with dxf2erl:start("filename.dxf"). from the erlang console.

The DXF file can be created in LibreCAD [6], QCAD [7], FreeCAD [8], OpenSCAD [9] and other CAD systems. Entities with higher thickness will be drawn last. Closed polylines will be filled areas in ex11.

Ports

Erlang is using ports to communicate with all the different hardware that you are connecting to your Raspberry Pi. In the image to the right I am using an ex11 line graph widget to show the load on my mains meter in the basement. The Erlang Port I'm using is programmable in the language Forth, read more about it here Forth. The fact that it is programmable makes it possible to read multiple sensors and command multiple actuators simultaneously and it is even possible to programmatically re-program the port from a running Erlang application. It is also very handy to debug because you can start the port standalone and issue commands to it in its own console window.

Sockets

You can also use TCP sockets to communicate with devices. A nice socket server is the pigpio daemon http://abyz.me.uk/rpi/pigpio/ . Easy to integrate in your Erlang application. More about this at https://github.com/skvamme/pigpio

-module(pigpio).
-author(skvamme).
-export([start/0,init/0,loop/1]).
-define(PIGPIO_IP,"192.168.0.20").
-define(PIGPIO_PORT,8888).
-define(UINT,32/little).

start() -> Pid = spawn_link(?MODULE,init,[]), {ok,Pid}.

init() ->
        io:format("New process: ~p~n", [?MODULE]),
        inets:start(),
        {ok,Socket} = gen_tcp:connect(?PIGPIO_IP, ?PIGPIO_PORT, [binary,{packet, 0}]),
        ok = gen_tcp:send(Socket,c(setmode,25,0)),
        ok = gen_tcp:send(Socket,c(getmode,25)),
        %callback:make(self(),33554432), % Start a callback to monitor pin 25 on a dedicated socket
        %timer:send_interval(10*1000, {timer}),
        loop(Socket).

loop(Socket) ->
        receive
                % {timer} -> ok = gen_tcp:send(Socket,c(br1)),
                %         ?MODULE:loop(Socket);
                {tcp,Socket,Data} ->
                        parse(Socket,Data),
                        ?MODULE:loop(Socket);
                {kwh,Sec} when Sec > 0 -> 
                        W = 3600000000 div Sec,
                        io:format("W: ~p~n",[W]),
                        ?MODULE:loop(Socket);
                Any -> io:format("~p got unknown msg: ~p~n",[?MODULE, Any]),
                        ?MODULE:loop(Socket)
        end.

%******************************************************************************
% pigpio response
%******************************************************************************
parse(_Socket,<<>>) -> ok;
parse(Socket,<<0:?UINT,P1:?UINT,P2:?UINT,0:?UINT,Rest/binary>>) ->
         io:format("setmode pin ~p mode ~p~n",[P1,P2]),
         parse(Socket,Rest);
parse(Socket,<<1:?UINT,P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
         io:format("getmode pin ~p mode ~p~n",[P1,P3]),
         parse(Socket,Rest);
parse(Socket,<<2:?UINT,P1:?UINT,P2:?UINT,0:?UINT,Rest/binary>>) ->
         io:format("setpullupdown pin ~p pud ~p~n",[P1,P2]),
         parse(Socket,Rest);
parse(Socket,<<3:?UINT,_P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
         io:format("read ~p~n",[P3]),
         parse(Socket,Rest);
parse(Socket,<<4:?UINT,P1:?UINT,P2:?UINT,0:?UINT,Rest/binary>>) ->
         io:format("write pin ~p level ~p~n",[P1,P2]),
         parse(Socket,Rest);
parse(Socket,<<10:?UINT,_P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
         io:format("readbits 0-31 ~.2B~n",[P3]),
         parse(Socket,Rest);
parse(Socket,<<17:?UINT,_P1:?UINT,_P2:?UINT,P3:?UINT,Rest/binary>>) ->
         io:format("hver ~p~n",[P3]),
         parse(Socket,Rest);
parse(_Socket,Response) -> io:format("Error: ~w~n",[Response]).

%******************************************************************************
% pigpio commands
%******************************************************************************
%c(hver) -> <<17:?UINT,0:?UINT,0:?UINT,0:?UINT>>;
%c(br1) -> <<10:?UINT,0:?UINT,0:?UINT,0:?UINT>>;
c(read,Gpio) -> <<3:?UINT,Gpio:?UINT,0:?UINT,0:?UINT>>;
c(getmode,Gpio) -> <<1:?UINT,Gpio:?UINT,0:?UINT,0:?UINT>>.
c(setmode,Gpio,Mode) -> <<0:?UINT,Gpio:?UINT,Mode:?UINT,0:?UINT>>; % Input = 0, Output = 1
c(write,Gpio,Level) -> <<4:?UINT,Gpio:?UINT,Level:?UINT,0:?UINT>>;
c(setpullupdown,Gpio,Pud) -> <<2:?UINT,Gpio:?UINT,Pud:?UINT,0:?UINT>>. % Off = 0, Down = 1, Up = 2