Difference between revisions of "Erlang"

From eLinux.org
Jump to: navigation, search
m (External interfaces)
m (PIGPIO)
(190 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 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 or more to compile (when you run "make").  
  
<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 http://www.erlang.org/download/otp_src_19.0.tar.gz
tar -xzvf otp_src_R16B01.tar.gz
+
tar -xzvf otp_src_19.0.tar.gz
cd otp_src_R16B01/
+
cd otp_src_19.0/
 
./configure
 
./configure
 
make
 
make
 
sudo make install
 
sudo make install
</pre></code>
+
cd ..
You start the interactive shell with erl and quit with Ctrl-g q
+
rm otp_src_19.0.tar.gz
== Introduction ==
+
sudo rm -R otp_src_19.0/
 +
</pre>
 +
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>
 +
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/otp_mibs/SKIP
 +
touch lib/parsetools/SKIP
 +
touch lib/percept/SKIP
 +
touch lib/reltool/SKIP
 +
touch lib/snmp/SKIP
 +
touch lib/typer/SKIP
 +
touch lib/wx/SKIP
 +
touch lib/xmerl/SKIP
 +
</pre>
 +
 
 +
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]
 +
 
 +
=== 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]
 +
 
 +
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]
 +
 
 +
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 [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.
  
Erlang is a programming language which has many features more commonly associated with an operating system than with a programming language: concurrent processes, scheduling, memory management, distribution, networking, etc. During the following days I will use Erlang on RPi to put a sensor value from Gertboard on the web using an interface written in [[Forth]].
+
=== 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 [[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.
  
== Concurrency ==
+
=== Sockets ===
Erlang has extremely lightweight processes whose memory requirements can vary dynamically. Processes have no shared memory and communicate by asynchronous message passing. Erlang supports applications with very large numbers of concurrent processes. No requirements for concurrency are placed on the host operating system.
+
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:
 +
<pre>sudo apt-get install owfs</pre>
 +
Configure owfs in /etc/owfs.conf
  
== Distribution ==
+
Then, save a file, onewire.erl with the following content:
Erlang is designed to be run in a distributed environment. An Erlang virtual machine is called an Erlang node. A distributed Erlang system is a network of Erlang nodes (typically one per processor). An Erlang node can create parallel processes running on other nodes, which perhaps use other operating systems. Processes residing on different nodes communicate in exactly the same was as processes residing on the same node.
+
<pre>
 +
-module(onewire).
 +
-export([start/0,init/0,loop/1]).  
  
== Robustness ==
+
start() -> spawn_link(?MODULE,init,[]).
Erlang has various error detection primitives which can be used to structure fault-tolerant systems. For example, processes can monitor the status and activities of other processes, even if these processes are executing on other nodes. Processes in a distributed system can be configured to fail-over to other nodes in case of failures and automatically migrate back to recovered nodes.
 
  
== Soft real-time ==
+
init() ->
Erlang supports programming "soft" real-time systems, which require response times in the order of milliseconds. Long garbage collection delays in such systems are unacceptable, so Erlang uses incremental garbage collection techniques.
+
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).
  
== Hot code loading ==
+
loop(Socket) ->
Many systems cannot be stopped for software maintenance. Erlang allows program code to be changed in a running system. Old code can be phased out and replaced by new code. During the transition, both old code and new code can coexist. It is thus possible to install bug fixes and upgrades in a running system without disturbing its operation.
+
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.
  
== Incremental code loading ==
+
%****************************************************************************************
Users can control in detail how code is loaded. In embedded systems, all code is usually loaded at boot time. In development systems, code is loaded when it is needed, even when the system is running. If testing uncovers bugs, only the buggy code need be replaced.
+
% 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>>.
 +
</pre>
 +
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.
  
== A Web Server ==
+
Another Erlang module can send this message to onewire: {ow_dir,<<"/",0>>,self()} and get back a directory listing.
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 to install it:
+
 
<code><pre>
+
== PIGPIO ==
sudo apt-get install git
+
Another nice socket server is the pigpio daemon http://abyz.co.uk/rpi/pigpio/ . Easy to integrate in your Erlang application. More about this at https://github.com/skvamme/pigpio
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 HTML:
 
<code><pre>
 
cd examples/hello_world/src/
 
cp hello_world.app.src ../../../ebin/hello_world.app
 
erlc *.erl
 
mv *.beam ../../../ebin/
 
cd ..
 
cp start.sh ../../
 
cd ../../
 
./start.sh
 
</pre></code>
 
Start a browser and point it to your RPi:8080, eg 192.168.0.178:8080
 
  
== External interfaces ==
+
<pre>
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 create an erlang module to pair with atlast forth, save it as gertboard.erl:
+
-module(pigpio).
<code><pre>
+
-author(skvamme).
-module(gertboard).
 
 
-export([start/0,init/0,loop/1]).
 
-export([start/0,init/0,loop/1]).
 +
-define(PIGPIO_IP,"192.168.0.20").
 +
-define(PIGPIO_PORT,8888).
 +
-define(UINT,32/little).
  
start() -> spawn_link(?MODULE,init,[]).
+
start() -> Pid = spawn_link(?MODULE,init,[]), {ok,Pid}.
  
 
init() ->
 
init() ->
  Port = open_port({spawn, "./priv/atlast -ikwh.atl"}, [binary]),
+
        io:format("New process: ~p~n", [?MODULE]),
  loop(Port).
+
        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
  
loop(Port) ->
+
</pre>
  receive
 
    Any ->
 
      io:format("~p got unknown msg: ~p~n",[?MODULE, Any]),
 
      ?MODULE:loop(Port)
 
  end.
 
</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.
 
 
[[Category: RaspberryPi]]
 
[[Category: RaspberryPi]]

Revision as of 01:50, 11 August 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 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 http://www.erlang.org/download/otp_src_19.0.tar.gz
tar -xzvf otp_src_19.0.tar.gz
cd otp_src_19.0/
./configure
make
sudo make install
cd ..
rm otp_src_19.0.tar.gz
sudo rm -R otp_src_19.0/

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/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/otp_mibs/SKIP
touch lib/parsetools/SKIP
touch lib/percept/SKIP
touch lib/reltool/SKIP
touch lib/snmp/SKIP
touch lib/typer/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 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