anchor.diff:  %    list_to_integer(binary_to_list(DeviceId)).
   
   parse_date(Year, Month, Day, Hour, Minute, Second) ->
  -    Date = {
  -      {
  -        list_to_integer(binary_to_list(Year)),
  -        list_to_integer(binary_to_list(Month)), 
  -        list_to_integer(binary_to_list(Day))
  -      },
  -      {
  -        list_to_integer(binary_to_list(Hour)), 
  -        list_to_integer(binary_to_list(Minute)),
  -        list_to_integer(binary_to_list(Second)) 
  -      }
  -     },
  +  Date = {
  +    {
  +      list_to_integer(binary_to_list(Year)),
  +      list_to_integer(binary_to_list(Month)),
  +      list_to_integer(binary_to_list(Day))
  +    },
  +    {
  +      list_to_integer(binary_to_list(Hour)),
  +      list_to_integer(binary_to_list(Minute)),
  +      list_to_integer(binary_to_list(Second))
  +    }
  +  },
     em_helper_time:datetime_to_utc(Date).
   
  -data_match(Data, Pattern) ->
  -  {match, List} = re:run(Data, Pattern),
  -  lists:reverse(lists:foldl(fun(Param, Res) ->
  -                                    [read_param(Param, Data) | Res]
  -                            end, [], List)).
  -
  -read_param({-1, 0}, _) ->
  -    void;
  -read_param({Pos, Len}, Data) ->
  -    binary:part(Data, Pos, Len).
  -
  +%% +RESP:GTFRI,020102,123456789012345,,0,0,1,1,0.0,0,0.0,130.000000,60.000000,20120101120400,0460,0000,18d8,6141,00,,20120101120400,11F0\$
  +%% +RESP:GTFRI,2C0204,867162020003125,GL300W,0,0,2,1,1.7,205,2867.0,-78.481127,-0.206828,20160215210433,0740,0000,7596,5891C,0.0,1,1.7,205,2867.0,-78.481127,-0.206828,20160215210503,0740,0000,7596,5891C,0.0,88,20160215210506,1E78\$
   test() ->
  -    Packet = <<"+RESP:GTFRI,020102,123456789012345,,0,0,1,1,0.0,0,0.0,130.000000,60.000000,20120101120400,0460,0000,18d8,6141,00,,20120101120400,11F0\$">>,
  -    {ok, Pattern} = re:compile(?PATTERN),
  -    parse(Packet, Pattern).
  +  %%Packet = <<"+RESP:GTFRI,2C0204,867162020003125,GL300W,0,0,2,1,1.7,205,2867.0,-78.481127,-0.206828,20160215210433,0740,0000,7596,5891C,0.0,1,1.7,205,2867.0,-78.481127,-0.206828,20160215210503,0740,0000,7596,5891C,0.0,88,20160215210506,1E78\$">>,
  +  Packet = <<"1,1.7,205,2867.0,-78.481127,-0.206828,20160215210433,0740,0000,7596,5891C,0.0">>,
  +  em_regexp:match(Packet, ?PATTERN_LOCATION).
positive.code: -author("Sergey Penkovsky <sergey.penkovsky@gmail.com>").
  
  -behaviour(ranch_protocol).
  
  -include("em_records.hrl").
  
  %% API
  -export([start_link/4]).
  -export([init/4]).
  -export([test/0]).
  
  -define(Header0, <<"(?:(?:\\+(?:RESP|BUFF):)|">>).
  -define(Header1, <<"(?:\\x00?\\x04,[A-Fa-f0-9]{4},[01],))">>).
  -define(Header2, <<"GT...,">>).
  -define(ProtocolVersion, <<"(?:[0-9a-fA-F]{6})?,">>). %% Protocol version
  -define(Imei, <<"(\\d{15}),.*,">>). %% IMEI
  -define(GpsAccuracy, <<"(\\d*),">>). %% GPS accuracy
  -define(Speed, <<"(\\d+.\\d)?,">>). %% Speed
  -define(Course, <<"(\\d+)?,">>).%% Course
  -define(Altitude, <<"(-?\\d+\\.\\d)?,">>). %% Altitude
  -define(Longitude, <<"(-?\\d+\\.\\d+),">>). %% Longitude
  -define(Latitude, <<"(-?\\d+\\.\\d+),">>).%% Latitude
  -define(Date, <<"(\\d{4})(\\d{2})(\\d{2})">>). %% Date (YYYYMMDD)
  -define(Time, <<"(\\d{2})(\\d{2})(\\d{2}),">>). %% Time (HHMMSS)
  -define(MCC, <<"(\\d{4})?,">>). %% MCC
  -define(MNC, <<"(\\d{4})?,">>). %% MNC
  -define(LAC, <<"([A-Fa-f0-9]{4})?,">>). %% LAC
  -define(Cell, <<"([A-Fa-f0-9]{4})?,">>). %% Cell
  -define(Odometer, <<"(?:(\\d+\\.\\d)?,">>). %% Odometer
  -define(Battery, <<"(\\d{1,3})?)?">>). %% Battery
  -define(Any, <<".*">>).
  
  -define(PATTERN,
    <<
      ?Header0/binary,
      ?Header1/binary,
      ?Header2/binary,
      ?ProtocolVersion/binary,
      ?Imei/binary,
      ?GpsAccuracy/binary,
      ?Speed/binary,
      ?Course/binary,
      ?Altitude/binary,
      ?Longitude/binary,
      ?Latitude/binary,
      ?Date/binary,
      ?Time/binary,
      ?MCC/binary,
      ?MNC/binary,
      ?LAC/binary,
      ?Cell/binary,
      ?Odometer/binary,
      ?Battery/binary,
      ?Any/binary
    >>
  ).
  
  -record(state, {protocol, transport, socket, timeout, device, pattern}).
  
  start_link(Ref, Socket, Transport, Opts) ->
    Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
    {ok, Pid}.
  
  init(Ref, Socket, Transport, Opts) ->
    ok = ranch:accept_ack(Ref),
    Protocol = proplists:get_value(protocol, Opts),
    {ok, Pattern} = re:compile(?PATTERN),
    loop(#state{protocol = Protocol, socket = Socket, transport = Transport, pattern = Pattern}).
  
  loop(State = #state{protocol = Protocol, socket = Socket, transport = Transport, pattern = Pattern}) ->
    case Transport:recv(Socket, 0, 5000) of
      {ok, Data} ->
        em_logger:info("[packet] unit: ip = '~s' data: ~s", [em_hardware:resolve(Socket), Data]),
        {Imei, PositionModel} = parse(Data, Pattern),
        case em_data_manager:get_device_by_uid(Imei) of
          {error, _Reason} ->
            em_logger:info("[packet] unit: ip = '~s' unknown device with imei = '~s'", [em_hardware:resolve(Socket), Imei]),
            Transport:close(Socket);
          {ok, Object} ->
            Position = PositionModel#position{
              deviceId = Object#device.id,
              protocol = atom_to_binary(Protocol, utf8),
              attributes = maps:merge(PositionModel#position.attributes, #{
                ?KEY_IP => em_hardware:resolve(Socket)
              })
            },
            em_logger:info("save message => unit: ip = '~s' id = '~w' imei = '~s' position: ~w", [em_hardware:resolve(Socket), Object#device.id, Imei, Position]),
            em_data_manager:create_position(Object, Position),
            loop(State#state{device = Object})
        end;
      _ ->
        Transport:close(Socket)
    end.
  
  % echo "4. gl200"
  % (echo -n -e "+RESP:GTFRI,020102,123456789012345,,0,0,1,1,0.0,0,0.0,130.000000,60.000000,20120101120400,0460,0000,18d8,6141,00,,20120101120400,11F0\$";) | nc -v localhost 5004
  parse(Data, Pattern) -> 
      case data_match(Data, Pattern) of
          [_, Imei, Validity, Speed, Course, Altitude, Longitude, Latitude, Year, Month, Day, Hour, Minute, Second,  Mcc, Mnc, Lac, Cell | _] ->
              Position = #position{
                deviceTime = parse_date(Year, Month, Day, Hour, Minute, Second),
                latitude = parse_coord(Latitude),
                longitude = parse_coord(Longitude),
                speed = parse_speed(Speed),
                course = parse_course(Course),
                altitude = parse_altitude(Altitude),
                valid = parse_valid(Validity),
                attributes = # {
                  ?KEY_MCC => parse_mcc(Mcc),
                  ?KEY_MNC => parse_mnc(Mnc),
                  ?KEY_LAC => parse_lac(Lac),
                  ?KEY_CELL => parse_cell(Cell)
                  %%<<"odometer">> => parse_odometer(Odometer),
                  %%<<"battery">> => parse_battery(Battery)
                }
              },
              {Imei, Position};
          _ ->
              {}
      end.
  
  parse_mcc(Mcc) ->
      Mcc.
  
  parse_mnc(Mnc) ->
      Mnc.
  
  parse_lac(Lac) ->
      Lac.
  
  parse_cell(Cell) ->
      Cell.
  
  %%parse_odometer(Odometer) ->
  %%    Odometer.
  
negative.code: %%    Battery.
  
  parse_altitude(Altitude) ->
    list_to_float(binary_to_list(Altitude)).
  
  parse_speed(Speed) ->
    list_to_float(binary_to_list(Speed)).
  
  parse_course(Course) ->
    list_to_integer(binary_to_list(Course)).
  
  parse_coord(Coord) ->
    list_to_float(binary_to_list(Coord)).
  
  parse_valid(Validity) when Validity == <<"1">> ->
    true;
  parse_valid(Validity) when Validity == <<"0">> ->
    false.
  
  
  
  