Difference between revisions of "Erlang"

From eLinux.org
Jump to: navigation, search
m (The native Erlang graphics library, ex11)
m (PIGPIO)
(46 intermediate revisions by the same user not shown)
Line 1: Line 1:
Erlang on Raspberry Pi, starting with a fresh Raspbian image (even Lite will do). Make sure you run raspi-config to enlarge the file system to fill your memory card. Erlang takes a couple of hours to compile (when you run "make").  
+
Erlang on Raspberry Pi, starting with a fresh Raspbian image (even Lite will do). Make sure you run [[RPi_raspi-config|raspi-config]] to enlarge the file system to fill your memory card. Erlang takes about 40 minutes to compile (when you run "make").  
  
 
<pre>
 
<pre>
Line 6: Line 6:
 
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_18.2.1.tar.gz
+
wget http://www.erlang.org/download/otp_src_18.3.tar.gz
tar -xzvf otp_src_18.2.1.tar.gz
+
tar -xzvf otp_src_18.3.tar.gz
cd otp_src_18.2.1/
+
cd otp_src_18.3/
 
./configure
 
./configure
 
make
 
make
 
sudo make install
 
sudo make install
 
cd ..
 
cd ..
sudo rm -R otp_src_18.2.1/
+
rm otp_src_18.3.tar.gz
 +
sudo rm -R otp_src_18.3/
 
</pre>
 
</pre>
If you don't want to install everything that comes with the standard Erlang package, you can save space and time just by putting a file with the name SKIP in every library you don't want/need in your raspberry pi. This is the applications I usually skip. Do this before you run ./configure and make.
+
If you don't want to install everything that comes with the standard Erlang package, you can save space and time by just putting a file with the name SKIP in every library you don't want/need in your raspberry pi. This is the applications I usually skip. Do this before you run ./configure and make.
 
<pre>
 
<pre>
 
touch lib/asn1/SKIP
 
touch lib/asn1/SKIP
Line 44: Line 45:
 
</pre>
 
</pre>
  
You start the interactive shell with erl and quit with Ctrl-g q. Read about Erlang at [http://learnyousomeerlang.com].
+
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]
  
== Introduction ==
+
=== Graphics ===
 +
[[File:Ex11_graphics.jpg|thumb|right|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). Experience soft realtime graphics. Get it here [https://github.com/skvamme/ex11]
  
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. Then I will show how to drive a small screen (5") directly from Erlang graphics library ex11 [https://github.com/skvamme/ex11].
+
Here's an implementation of ex11 on a mobile phone, note the concurrent animations. Each digit is a separate Erlang process. [https://www.youtube.com/watch?v=hOotkglKZ94]
  
== A Web Server ==
+
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.
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; Install git
 
<pre>
 
sudo apt-get install git
 
</pre>
 
then install the cowboy web server and test run it. Follow the instructions in the [http://ninenines.eu/docs/en/cowboy/1.0/guide/getting_started/ | Getting Started] section. Don't edit src/hello_erlang.app.src, it's not there, edit ebin/yourname.app instead. This will be fixed soon in the Getting started guide.
 
  
Replace the file nameofyourserver_app.erl with the following code and replace housserver with the name of your server:
+
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.
  
<pre>
+
=== Ports ===
-module(houseserver_app).
+
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 [[Forth|here]]. 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.
-behaviour(application).
 
  
-export([start/2]).
+
=== Sockets ===
-export([stop/1]).
+
You can also use TCP sockets to communicate with devices. 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:  
 
 
start(_Type, _Args) ->
 
Dispatch = cowboy_router:compile([
 
{'_', [
 
{"/", toppage_handler, []},
 
{"/kwh", ws_handler_1, []},
 
{"/static/[...]", cowboy_static, {priv_dir, houseserver, [<<"static">>],[
 
{mimetypes, cow_mimetypes, all}
 
]}}
 
]}
 
]),
 
{ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
 
{env, [{dispatch, Dispatch}]}
 
]),
 
houseserver_sup:start_link().
 
 
 
stop(_State) ->
 
ok.
 
</pre>
 
 
 
This dispatcher will run toppage_handler.erl when called with a plain URL. It will run ws_handler_1.erl when called with URL/kwh, and when called with URL/static/somefile.html it will look for somefile.html in priv/static/
 
 
 
My ws_handler_1 is a websocket handler to let me display kwh-load in realtime on a web page. I'll add that to this wiki at a later time.
 
 
 
== 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 src/toppage_handler.erl. Replace toppage_handler.erl with the following:
 
<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) ->
 
  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.
 
</pre>
 
and compile the file:
 
<pre>
 
make
 
</pre>
 
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:
 
<pre>
 
cd ~/cowboy
 
mkdir priv
 
cp ../atlast-1.2/atlast ./priv
 
</pre>
 
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:
 
<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 )
 
: w 25 getkwh 2drop 25 getkwh 36 * swap 100 / / ." "w=" . cr ;
 
</pre>
 
OK, now we just have to start it up (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). Remember to change the name "houseserver" to the name of your server.
 
<pre>
 
make
 
 
 
sudo ./_rel/houseserver_release/bin/houseserver_release console
 
</pre>
 
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. And one final note: You have to run "make" even if you only changed an HTML-file, I scratched my head a bit about that before I got it, have fun :)
 
 
 
Note: The suggested way to call the port directly from 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.
 
 
 
 
 
=== 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 src/toppage_handler.erl with the following (then follow the Gertboard example for compiling and running):
 
<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) ->
 
  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.
 
 
 
</pre>
 
Note: The suggested way to call the port directly from 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.
 
<pre>
 
-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.
 
</pre>
 
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 [http://www.videohelp.com/tools/tsMuxeR] for that:
 
<pre>
 
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"),
 
</pre>
 
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.
 
 
 
=== 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:  
 
 
<pre>sudo apt-get install owfs</pre>
 
<pre>sudo apt-get install owfs</pre>
 
Configure owfs in /etc/owfs.conf
 
Configure owfs in /etc/owfs.conf
Line 305: Line 126:
 
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.
  
== The native Erlang graphics library, ex11 ==
+
== PIGPIO ==
 +
Another nice socket server is the pigpio daemon http://abyz.co.uk/rpi/pigpio/ . Easy to integrate in your Erlang application. More about his at https://github.com/skvamme/pigpio
 +
 
 +
<pre>
 +
-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.
  
Make sure you have a working ATLAST Forth Erlang port by following the examples in the  [[Forth]] section and the Gertboard example above. You don't have to install Cowboy, just do the changes to ATLAST Forth to make it start without printing the banner.  
+
%******************************************************************************
 +
% 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]).
  
Then install ex11, follow the installation instructions from my Github page [https://github.com/skvamme/ex11].
+
%******************************************************************************
 +
% 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
  
In directory ex11, create a directory priv and copy atlast to it:
 
<pre>
 
cd ~/ex11
 
mkdir priv
 
cp ../atlast-1.2/atlast ./priv
 
</pre>
 
Create a file kwh.atl and put it in the same priv directory with the word definitions for getting W load values from Gertboard:
 
<pre>
 
: w 25 getkwh 2drop 25 getkwh 36 * swap 100 / / ." "w=" . cr ;
 
 
</pre>
 
</pre>
  
This is not complete, more to follow
 
 
[[Category: RaspberryPi]]
 
[[Category: RaspberryPi]]

Revision as of 10:51, 17 May 2016

Erlang on Raspberry Pi, starting with a fresh Raspbian image (even Lite will do). Make sure you run raspi-config to enlarge the file system to fill your memory card. Erlang takes about 40 minutes 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 http://www.erlang.org/download/otp_src_18.3.tar.gz
tar -xzvf otp_src_18.3.tar.gz
cd otp_src_18.3/
./configure
make
sudo make install
cd ..
rm otp_src_18.3.tar.gz
sudo rm -R otp_src_18.3/

If you don't want to install everything that comes with the standard Erlang package, you can save space and time by just putting a file with the name SKIP in every library you don't want/need in your raspberry pi. This is the applications I usually skip. Do this before you run ./configure and make.

touch lib/asn1/SKIP
touch lib/cosEvent/SKIP
touch lib/cosEventDomain/SKIP
touch lib/cosFileTransfer/SKIP
touch lib/cosNotification/SKIP
touch lib/cosProperty/SKIP
touch lib/cosTime/SKIP
touch lib/cosTransactions/SKIP
touch lib/diameter/SKIP
touch lib/eldap/SKIP
touch lib/ic/SKIP
touch lib/gs/SKIP
touch lib/megaco/SKIP
touch lib/orber/SKIP
touch lib/ose/SKIP
touch lib/otp_mibs/SKIP
touch lib/parsetools/SKIP
touch lib/percept/SKIP
touch lib/reltool/SKIP
touch lib/snmp/SKIP
touch lib/test_server/SKIP
touch lib/typer/SKIP
touch lib/webtool/SKIP
touch lib/wx/SKIP
touch lib/xmerl/SKIP

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

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). Experience soft realtime graphics. Get it here [4]

Here's an implementation of ex11 on a mobile phone, note the concurrent animations. Each digit is a separate Erlang process. [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. 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. 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.

PIGPIO

Another nice socket server is the pigpio daemon http://abyz.co.uk/rpi/pigpio/ . Easy to integrate in your Erlang application. More about his 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