Difference between revisions of "Erlang"

From eLinux.org
Jump to: navigation, search
m (raspivid)
m (Expansion. Some copy editing.)
Line 1: Line 1:
Erlang on RPi, starting with a fresh wheezy-raspbian image. Erlang takes a couple of hours to compile (when you run "make").
+
Erlang on Raspberry Pi, starting with a fresh Wheezy-Raspbian image. Erlang takes a couple of hours to compile (when you run "make").
  
 
<code><pre>
 
<code><pre>
Line 12: Line 12:
 
sudo make install
 
sudo make install
 
</pre></code>
 
</pre></code>
You start the interactive shell with erl and quit with Ctrl-g q. Read about Erlang here [http://learnyousomeerlang.com]
+
You start the interactive shell with erl and quit with Ctrl-g q. Read about Erlang at [http://learnyousomeerlang.com].
 +
 
 
== Introduction ==
 
== 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.
+
On this page I will show how to use Erlang on Raspberry Pi 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 ==
 
== A Web Server ==
Erlang is a perfect language to use for programming a web server, and that is exactly what Loïc Hoguin did. [http://www.ninenines.eu] 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:
+
Erlang is a perfect language to use for programming a web server, and that is exactly what Loïc Hoguin did. [http://www.ninenines.eu] Cowboy is one of the most scalable web servers there is and it's also small, so it fits nicely in a Raspberry Pi. 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>
 
<code><pre>
 
sudo apt-get install git
 
sudo apt-get install git
Line 34: Line 35:
 
./start.sh
 
./start.sh
 
</pre></code>
 
</pre></code>
Start a browser and point it to your RPi:8080, eg 192.168.0.178:8080
+
Start a browser and point it to your Raspberry Pi:8080, for example, 192.168.0.178:8080.
  
 
== External interfaces - Gertboard ==
 
== 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:
+
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>
 
<code><pre>
 
%% @doc Hello world handler.
 
%% @doc Hello world handler.
Line 72: Line 73:
 
Now, we need to put atlast in the ./priv directory, but first we have to get rid of the banner and the prompt.
 
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.
+
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.
  
 
In directory cowboy, create a directory priv and copy atlast to it:
 
In directory cowboy, create a directory priv and copy atlast to it:
Line 86: Line 87:
 
: kw 3.6 25 getkwh 2drop 25 getkwh float 2 roll float 2swap f/ f/ ." "kw=" f. cr ;
 
: 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 )
+
( The same thing done with integers only. Here the result is in W )
 
: w 25 getkwh 2drop 25 getkwh 36 * swap 100 / / ." "w=" . cr ;
 
: w 25 getkwh 2drop 25 getkwh 36 * swap 100 / / ." "w=" . cr ;
 
</pre></code>
 
</pre></code>
Ok, now we just have to call start.sh again ( sudo is needed by the Gertboard driver, not by Cowboy, and for pure simplicity I take another shortcut and starts the Cowboy with sudo rights. Don't do this in a production environment).
+
OK, now we just have to call start.sh again (sudo is needed by the Gertboard driver, not by Cowboy, and for pure simplicity I take another shortcut and starts the Cowboy with sudo rights. Don't do this in a production environment).
 
<code><pre>
 
<code><pre>
 
sudo ./start.sh
 
sudo ./start.sh
 
</pre></code>
 
</pre></code>
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 :)
+
Point the browser to 192.168.0.178:8080 or whatever IP address you have on your Raspberry Pi. After a few seconds the W load should show up. I stop here as this is not the right place 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.
 
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.
Line 99: Line 100:
 
== External Interfaces - Pi Camera ==
 
== External Interfaces - Pi Camera ==
 
=== raspistill ===
 
=== raspistill ===
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).
+
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):
 
<code><pre>
 
<code><pre>
 
%% @doc Hello world handler.
 
%% @doc Hello world handler.
Line 136: Line 137:
  
 
=== raspivid ===
 
=== raspivid ===
raspivid can create an h264 video file, and with the command line parameter -o - it will stream to stdout. Below is an Erlang module that takes this stream, slices it in 10 second chunks and sends these slices over a websocket connection to a webserver.
+
raspivid can create an H.264 video file, and with the command line parameter -o - it will stream to stdout. Below is an Erlang module that takes this stream, slices it in 10 second chunks and sends these slices over a websocket connection to a webserver.
 
<code><pre>
 
<code><pre>
 
-module(raspivid).
 
-module(raspivid).
Line 181: Line 182:
 
os:cmd("tsMuxeR my_media.meta my_media.ts"),
 
os:cmd("tsMuxeR my_media.meta my_media.ts"),
 
</pre></code>
 
</pre></code>
You can play the file my_media.ts as it is, or you can put it in an m3u8 playlist. If you want to have one large media file you can just concatenate the 10 second h264 chunks before running tsMuxeR.
+
You can play the file my_media.ts as it is, or you can put it in an m3u8 playlist. If you want to have one large media file you can just concatenate the 10 second H.264 chunks before running tsMuxeR.
  
 
== External Interfaces - Onewire ==
 
== External Interfaces - Onewire ==
Line 244: Line 245:
 
</pre></code>
 
</pre></code>
 
Compile the file with: erlc onewire.erl
 
Compile the file with: erlc onewire.erl
Start an Erlang console with erl and type onewire:start(). and you should see a listing of your onewire network.
+
Start an Erlang console with erl and type onewire:start(), and you should see a listing of your onewire network.
  
 
Another Erlang module can send this message to onewire: {ow_dir,<<"/",0>>,self()} and get back a directory listing.
 
Another Erlang module can send this message to onewire: {ow_dir,<<"/",0>>,self()} and get back a directory listing.
  
 
[[Category: RaspberryPi]]
 
[[Category: RaspberryPi]]

Revision as of 06:44, 7 September 2014

Erlang on Raspberry Pi, starting with a fresh Wheezy-Raspbian image. Erlang takes a couple of hours to compile (when you run "make").

sudo apt-get install wget
sudo apt-get install libssl-dev
sudo apt-get install ncurses-dev
wget http://www.erlang.org/download/otp_src_R16B03.tar.gz
tar -xzvf otp_src_R16B03.tar.gz
cd otp_src_R16B03/
./configure
make
sudo make install

You start the interactive shell with erl and quit with Ctrl-g q. Read about Erlang at [1].

Introduction

On this page I will show how to use Erlang on Raspberry Pi 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. [2] Cowboy is one of the most scalable web servers there is and it's also small, so it fits nicely in a Raspberry Pi. I was thinking of using it to display the kW load from my Atlast Forth interface to Gertboard. Here is how I did it; first install the cowboy web server and test run it:

sudo apt-get install git
git clone git://github.com/extend/cowboy.git
cd cowboy
make

To make it run you first have to tell it to accept HTTP:

cd ~/cowboy/ebin
cp examples/hello_world/src/hello_world.app.src ./hello_world.app
erlc ../examples/hello_world/src/*.erl
cd ..
cp examples/hello_world/start.sh .
./start.sh

Start a browser and point it to your Raspberry Pi:8080, for example, 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 Atlast Forth in the file examples/hello_world/src/toppage_handler.erl. Replace toppage_handler.erl with the following:

%% @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) ->
  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) ->
        ok.

cd to ebin and compile the file:

cd ~/cowboy/ebin
erlc ../examples/hello_world/src/toppage_handler.erl

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.

In directory cowboy, create a directory priv and copy atlast to it:

cd ~/cowboy
mkdir priv
cp ../atlast-1.2/atlast ./priv

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:

( 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 )
: w 25 getkwh 2drop 25 getkwh 36 * swap 100 / / ." "w=" . cr ;

OK, now we just have to call start.sh again (sudo is needed by the Gertboard driver, not by Cowboy, and for pure simplicity I take another shortcut and starts the Cowboy with sudo rights. Don't do this in a production environment).

sudo ./start.sh

Point the browser to 192.168.0.178:8080 or whatever IP address you have on your Raspberry Pi. After a few seconds the W load should show up. I stop here as this is not the right place 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.

External Interfaces - Pi Camera

raspistill

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):

%% @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) ->
  Port = open_port({spawn, "/opt/vc/bin/raspistill -t 2 -w 1024 -h 768 -o -"}, [binary]),
  Str = loop(Port,<<>>), 
  {ok, Req2} = cowboy_req:reply(200, [], Str, Req),
  {ok, Req2, State}.

terminate(_Reason, _Req, _State) ->
        ok.

loop(Port,Frame) ->
  receive			
    {Port, {data, Chunk}} -> 
      Size = byte_size(Chunk) - 2,
      case Chunk of 
        <<_:Size/binary,255,217>> -> Framestring = base64:encode_to_string(<<Frame/binary,Chunk/binary>>),
                  "<html><img src = 'data:image/jpeg;base64," ++ Framestring ++ "'></html>";
    _ -> loop(Port,<<Frame/binary,Chunk/binary>>)
  end
  after 20*1000 -> "<html>timeout</html>"
  end.

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.

raspivid

raspivid can create an H.264 video file, and with the command line parameter -o - it will stream to stdout. Below is an Erlang module that takes this stream, slices it in 10 second chunks and sends these slices over a websocket connection to a webserver.

-module(raspivid).
-export([start/0,init/0,loop/1]).
-define(BOUNDARY,<<0,0,0,1,39,66,128,40,149,160>>).
%%*****************************************************************************************************************
% Takes a stream from raspivid and sends 10 second chunks over a websocket to a webserver as a binary message
%%*****************************************************************************************************************
start() -> spawn_link(?MODULE,init,[]).

init() ->
	process_flag(trap_exit, true),
        crypto:start(),
        ssl:start(),
        websocket_client:start_link("ws://192.168.0.2/websockets", ?MODULE, []).
	open_port({spawn, "raspivid -n -t 600000 -g 300 -w 640 -h 480 -pf 'baseline' -fps 30 -ih -qp 30 -o -"}, [binary,eof]),
	loop(<<>>).

loop(Data) ->
	receive
		{_, {data, Data1}}  ->
			Rest1 = case binary:split(<<Data/binary,Data1/binary>>,?BOUNDARY,[global]) of 
				[_,Chunk,Rest] -> websocket_client:cast(self(),{binary,<<?BOUNDARY/binary,Chunk/binary>>}),
									<<?BOUNDARY/binary,Rest/binary>>;
				_ -> <<Data/binary,Data1/binary>>
			end,
			?MODULE:loop(Rest1);
		{_,eof} -> ok;
		{'EXIT', _Pid0, Why} -> io:format("~p crash: ~p~n",[?MODULE,Why]);
		_Any -> 
			?MODULE:loop(Data)
	after 15000 ->
		io:format("~p timeout~n",[?MODULE])
	end.

Save the code in a file raspivid.erl and compile.

I am using https://github.com/jeremyong/websocket_client as websocket client and Cowboy as websocket server. On the server side you just have to save each chunk in a file and enclose it in a ts-container. I use tsMuxeR [3] for that:

file:write_file("my_media.h264", Bin),
Meta = "MUXOPT --no-pcr-on-video-pid --new-audio-pes --vbr  --vbv-len=500
V_MPEG4/ISO/AVC, my_media.h264, fps=25, ar=As source",
file:write_file("my_media.meta", list_to_binary(Meta)),
os:cmd("tsMuxeR my_media.meta my_media.ts"),

You can play the file my_media.ts as it is, or you can put it in an m3u8 playlist. If you want to have one large media file you can just concatenate the 10 second H.264 chunks before running tsMuxeR.

External Interfaces - Onewire

This is a simple owserver client. It can call dir and print a directory listing from a onewire network. First, you have to install owfs:

sudo apt-get install owfs

Configure owfs in /etc/owfs.conf

Then, save a file, onewire.erl with the following content:

-module(onewire).
-export([start/0,init/0,loop/1]). 

start() -> spawn_link(?MODULE,init,[]).

init() ->
	io:format("New process: ~p~n", [?MODULE]),
	inets:start(),
	{ok,Socket} = gen_tcp:connect("localhost", 4304, [binary, {packet, 0}]),
	Version = 0,
	Type = 10,
	Control_flags = 36,
	Size = 1024,
	Offset = 0,
	Raw = <<"/",0>>,
	Payload = size(Raw),
	ok = gen_tcp:send(Socket,<<Version:32,Payload:32,Type:32,Control_flags:32,Size:32,Offset:32,Raw/binary>>),
	loop(Socket).

loop(Socket) ->
	receive
		{tcp,Socket,Bin} -> Dir = get_directory(Bin),
			io:format("In ~p: Dir is: ~p~n",[?MODULE,Dir]),
			?MODULE:loop(Socket);
		{ow_dir,Raw,Pid} -> 
			Version = 0,
			Type = 10, % get
			Control_flags = 36,
			Size = 1024,
			Offset = 0,
			Payload = size(Raw),
			ok = gen_tcp:send(Socket,<<Version:32,Payload:32,Type:32,Control_flags:32,Size:32,Offset:32,Raw/binary>>),
			receive {tcp,Socket,Bin} -> Dir = get_directory(Bin), Pid ! {ow_dir,Dir} after 10000 -> ok end,
			?MODULE:loop(Socket);
		{tcp_closed,_} -> ok;
		Any -> io:format("~p got unknown msg: ~p~n",[?MODULE, Any]),
			?MODULE:loop(Socket)
	end.

%****************************************************************************************
% Function get_directory(Bin)
%****************************************************************************************
get_directory(Bin) ->
	get_dir(Bin,<<>>).
	
get_dir(<<>>,Dir) -> Dir;
get_dir(<<0,0,0,0,0,0,0,0, Return_value:32/signed-integer,Rest/binary>>,_Dir) when Return_value /= 0 -> 
	io:format("Return value is: ~p and Rest is: ~p~n",[Return_value,Rest]);
get_dir(<<_Version:32,_Payload:32,0:32,_Control_flags:32,Size:32,_Offset:32,Raw:Size/binary,0,Rest/binary>>,Dir) ->
	get_dir(Rest,<<Dir/binary,Raw/binary>>);
get_dir(<<_Version:32,Payload:32,Return_value:32,_Control_flags:32,_Size:32,_Offset:32,Raw:Payload/binary>>,Dir) ->
	io:format("Return value is: ~p and Raw is: ~p~n",[Return_value,Raw]),
	<<Dir/binary,Raw/binary>>.

Compile the file with: erlc onewire.erl Start an Erlang console with erl and type onewire:start(), and you should see a listing of your onewire network.

Another Erlang module can send this message to onewire: {ow_dir,<<"/",0>>,self()} and get back a directory listing.