Thursday, November 29, 2007

Parsing take #3

I want to make more structured messages so I parse the lists using a function I call compose/1 for this I again use Erlangs superior pattern matching.

So for GPGSA the compose looks like:


compose(["GPGSA",A,B,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12,D,E,F]) ->
{gsa,
{auto,list_to_atom(A)},
{fix,list_to_integer(B)},
{satellites,satellites([C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12],[])},
{pdop,list_to_float(D)},
{horizdi,list_to_float(E)},
{vertdi,list_to_float(F)}};


This will only match the lists with "GPGSA" sentences (my GPS device only submits the sentences GPGSA,GPGGA,GPRMC,GPGSV).

The structured data I use are simple tuples, where I use Erlang atoms as identifiers for the fields, as you can see I also translate the integer data into integers (and for other messages to float values). A helper function is used so I do not repeat my self to much:

satellites([[] | _], Satellites) ->
Satellites;
satellites([A | Rest], Satellites) ->
satellites(Rest, [list_to_integer(A) | Satellites]).


The rest of compose for parsing the remaining sentences looks like:

compose(["GPGGA",A,B1,B2,C1,C2,D,E,F,G1,G2,H1,H2,I,J]) ->
{gga,
{fixtime,A},
{lattitude,[list_to_float(B1)/100,list_to_atom(B2)]},
{longitude,[list_to_float(C1)/100,list_to_atom(C2)]},
{quality,list_to_integer(D)},
{numberofsatellites,list_to_integer(E)},
{horizdi,list_to_float(F)},
{altitude,[list_to_float(G1),list_to_atom(G2)]},
{geoidheight,[list_to_float(H1),list_to_atom(H2)]},
{timesincedgps,list_to_float(I)},
{dgpsstation,J}};

compose(["GPRMC",A,B,C1,C2,D1,D2,E,F,G,H1,H2]) ->
{rmc,
{fixtime,A},
{status,list_to_atom(B)},
{lattitude,[list_to_float(C1)/100,list_to_atom(C2)]},
{longitude,[list_to_float(D1)/100,list_to_atom(D2)]},
{speed,list_to_float(E)},
{trackangle,list_to_float(F)},
{date,G},
{magneticvariation,[H1,H2]}};

compose(["GPGSV",A,B,C | SateliteData ]) ->
{gsv,
{numberofsentences,list_to_integer(A)},
{number,list_to_integer(B)},
{numberofsatellites,list_to_integer(C)},
satellitedata(SateliteData, [])}.


I divide the longitude/lattitudes by 100 as I wish that they closely match the expected format for Google maps.

The GPGSV sentences are variable length so I use a helper function for parsing those:


satellitedata([D,E,F,G | SateliteData ], Satellites) ->
satellitedata(SateliteData,
[{sattelite,list_to_integer(D)},
{elevation,list_to_integer(E)},
{azimuth,list_to_integer(F)},
{snr,list_to_integer(G)} | Satellites]);
satellitedata([], Satellites) ->
Satellites.


My final loop/1 looks like this:

loop(Port) ->
receive
{Port, {data, Data}} ->
case Data of
<<"$", Rest/binary>> ->
Tokens = tokenize (Rest),
Record = compose(Tokens),
io:format("~p~n", [Record]);
_ ->
sl:setopt(Port, mode, line),
sl:update(Port)
end,
loop(Port);
stop ->
exit(normal);
{'EXIT', Port, Reason} ->
exit({port_terminated, Reason})
end.


The resulting messages are reported in the shell like:

{rmc,{fixtime,"214612.620"},
{status,'A'},
{lattitude,[29.9792,'N']},
{longitude,[31.1343,'E']},
{speed,9.00000e-2},
{trackangle,81.9100},
{date,"291107"},
{magneticvariation,[[],[]]}}
{gga,{fixtime,"214613.620"},
{lattitude,[29.9792,'N']},
{longitude,[31.1343,'E']},
{quality,1},
{numberofsatellites,4},
{horizdi,4.00000},
{altitude,[146.2000,'M']},
{geoidheight,[0.0000,'M']},
{timesincedgps,0.00000e+0},
{dgpsstation,"0000"}}
{gsa,{auto,'A'},
{fix,3},
{satellites,[2,23,20,31]},
{pdop,7.30000},
{horizdi,4.00000},
{vertdi,6.00000}}
{rmc,{fixtime,"214613.620"},
{status,'A'},
{lattitude,[56.2778,'N']},
{longitude,[10.2091,'E']},
{speed,0.100000},
{trackangle,100.610},
{date,"291107"},
{magneticvariation,[[],[]]}}


Note I still need to parse the dates and times into some nice representations.
I will next time make an additional process which will receive these messages and report only the position data in a format compatible with Google maps

1 comment:

Anonymous said...

Hi,
I didn't find the tokenize function in Erlang. Could you tell me where I can find it ?
Thank's