Friday, November 9, 2007

Reading serial data

The way to communicate with foreign systems from Erlang are through so called Ports. From Joe Armstrongs book the recommended template for doing that goes something like this:

-module(gpsdriver).
-export([start/0, stop/0]).

start() ->
spawn(fun() ->
register(gps,self()),
process_flag(trap_exit, true),
%%openport
loop(Port)
end).

stop() ->
gps ! stop.

loop(Port) ->
receive
{Port, {data, Data}} ->
case Data of
_ ->
io:format("Sum: ~p~n", [Data])
end,
loop(Port);
stop ->
exit(normal);
{'EXIT', Port, Reason} ->
exit({port_terminated, Reason})
end.


The reason is that the process creating the Port is also the one receiving messages from it.
The code above is slightly simplified, as it only accepts stop messages or Data messages from the port (and process crash messages).

Now to read data from the serial connection, I of course had to read the sl (somewhat sparse) documentation (and the gps device specs). This boils down to communication settings 4800,N,8,1 from the device specs and discovering that it binds to the device /dev/ttyUSB0 on my Linux (remember that tty devices are usually only readable from root, so either do the chmod 666 or run the erl shell as root). From the sl docs a Port is created using start and options set using setopt, so my final start function ends up like (also setting options like receive buffer size, hardware flow control, and an undocumented feature called line mode, which makes the driver break messages on line boundaries):

start() ->
spawn(fun() ->
register(gps,self()),
process_flag(trap_exit, true),
Port = sl:start(),
sl:setopt(Port, dev, "/dev/ttyUSB0"),
sl:setopt(Port, baud, 4800),
sl:setopt(Port, csize, 8),
sl:setopt(Port, stopB, 1),
sl:setopt(Port, parity, 0),
sl:setopt(Port, hwflow, true),
sl:setopt(Port, bufsz, 100),
sl:setopt(Port, mode, line),
sl:update(Port),
sl:open(Port),
loop(Port)
end).


Remember to put the module in a file with the name of the module and extension .erl, also module names starts with lower-case (upper-case starting names in Erlang are reserved for variables).

Then in the Erlang shell typing:

c(gpsdriver).
gpsdriver:start().

Results in lines like:
"$GPGGA,071852.080,2997.9203,N,03113.4374,E,1,05,2.2,138.8,M,138.8,M,0.0,0000*4D\r\n" "$GPGSA,A,3,17,12,15,28,18,,,,,,,,5.0,2.2,4.5*34\r\n" ...

Stopping the flow using gpsdriver:stop().

Next: parsing time!

No comments: