anchor.diff:  -author("Sergey Penkovsky <sergey.penkovsky@gmail.com>").
   
   -behaviour(ranch_protocol).
  +-behaviour(gen_server).
   
   -include("em_records.hrl").
   
   %% API
   -export([start_link/4]).
  --export([init/4]).
   -export([test/0]).
   
  --define(PATTERN, <<"(?:\\$?)([A-Z]*),(.*)">>).
  -
  --define(PGID_PATTERN, <<"\\$PGID,(\\d*)\\*([0-9,a-f,A-F]{2})">>).
  --define(PCPTI_PATTERN, <<"\\$PCPTI,(\\d*),.*">>).
  --define(GPFID_PATTERN, <<"\\$GPFID(\\d*)">>).
  --define(IMEI_PATTERN, <<"IMEI(\\d*)">>).
  -
  --define(GPRMC_HEADER, <<"\\$GPRMC,">>).
  --define(GPRMC_TIME, <<"(\\d{2})(\\d{2})(\\d{2})\\.?\\d*,">>).   %% Time (HHMMSS.SSS)
  --define(GPRMC_VALIDITY, <<"([AV]),">>).   %% Validity
  --define(GPRMC_LATITUDE, <<"(\\d{2})(\\d{2}\\.\\d+),">>).   %% Latitude (DDMM.MMMM)
  --define(GPRMC_LATITUDE_TYPE, <<"([NS]),">>).   %%
  --define(GPRMC_LONGITUDE, <<"(\\d{3})(\\d{2}\\.\\d+),">>).   %% Longitude (DDDMM.MMMM)
  --define(GPRMC_LONGITUDE_TYPE, <<"([EW]),">>).   %%
  --define(GPRMC_SPEED, <<"(\\d+\\.?\\d*)?,">>).   %% Speed
  --define(GPRMC_COURSE, <<"(\\d+\\.?\\d*)?,">>).   %% Course
  --define(GPRMC_DATE, <<"(\\d{2})(\\d{2})(\\d{2})">>).   %% Date (DDMMYY)
  --define(GPRMC_ANY, <<".+">>).
  -
  --define(GPRMC_PATTERN,
  -  <<
  -  ?GPRMC_HEADER/binary,
  -  ?GPRMC_TIME/binary,
  -  ?GPRMC_VALIDITY/binary,
  -  ?GPRMC_LATITUDE/binary,
  -  ?GPRMC_LATITUDE_TYPE/binary,
  -  ?GPRMC_LONGITUDE/binary,
  -  ?GPRMC_LONGITUDE_TYPE/binary,
  -  ?GPRMC_SPEED/binary,
  -  ?GPRMC_COURSE/binary,
  -  ?GPRMC_DATE/binary,
  -  ?GPRMC_ANY/binary
  -  >>
  -).
  -
  --define(GPGGA_HEADER, <<"\\$GPGGA,">>).   %%
  --define(GPGGA_TIME, <<"(\\d{2})(\\d{2})(\\d{2})\\.?\\d*,">>).   %% Time
  --define(GPGGA_LATITUDE, <<"(\\d{2})(\\d{2}\\.\\d+),">>).   %% Latitude
  --define(GPGGA_LATITUDE_TYPE, <<"([NS]),">>).   %%
  --define(GPGGA_LONGITUDE, <<"(\\d{3})(\\d{2}\\.\\d+),">>).   %% Longitude
  --define(GPGGA_LONGITUDE_TYPE, <<"([EW]),">>).   %%
  --define(GPGGA_ANY, <<".+">>).   %%
  -
  --define(GPGGA_PATTERN,
  -  <<
  -  ?GPGGA_HEADER/binary,
  -  ?GPGGA_TIME/binary,
  -  ?GPGGA_LATITUDE/binary,
  -  ?GPGGA_LATITUDE_TYPE/binary,
  -  ?GPGGA_LONGITUDE/binary,
  -  ?GPGGA_LONGITUDE_TYPE/binary,
  -  ?GPGGA_ANY/binary
  -  >>
  -).
  -
  --define(GPRMA_HEADER, <<"\\$GPRMA,">>). %%
  --define(GPRMA_VALIDITY, <<"([AV]),">>). %% Validity
  --define(GPRMA_LATITUDE, <<"(\\d{2})(\\d{2}\\.\\d+),">>). %% Latitude
  --define(GPRMA_LATITUDE_TYPE, <<"([NS]),">>). %%
  --define(GPRMA_LONGITUDE, <<"(\\d{3})(\\d{2}\\.\\d+),">>). %% Longitude
  --define(GPRMA_LONGITUDE_TYPE, <<"([EW]),,,">>). %%
  --define(GPRMA_SPEED, <<"(\\d+\\.?\\d*)?,">>). %% Speed
  --define(GPRMA_COURSE, <<"(\\d+\\.?\\d*)?,">>). %% Course
  --define(GPRMA_ANY, <<".+">>). %%
  -
  --define(GPRMA_PATTERN,
  -  <<
  -  ?GPRMA_HEADER/binary,
  -  ?GPRMA_VALIDITY/binary,
  -  ?GPRMA_LATITUDE/binary,
  -  ?GPRMA_LATITUDE_TYPE/binary,
  -  ?GPRMA_LONGITUDE/binary,
  -  ?GPRMA_LONGITUDE_TYPE/binary,
  -  ?GPRMA_SPEED/binary,
  -  ?GPRMA_COURSE/binary,
  -  ?GPRMA_ANY/binary
  -  >>
  -).
  -
  --define(TRCCR_HEADER, <<"\\$TRCCR,">>). %%
  --define(TRCCR_DATE, <<"(\\d{4})(\\d{2})(\\d{2})">>). %% Date (YYYYMMDD)
  --define(TRCCR_TIME, <<"(\\d{2})(\\d{2})(\\d{2})\\.?\\d*,">>). %% Time (HHMMSS.SSS)
  --define(TRCCR_VALIDITY, <<"([AV]),">>). %% Validity
  --define(TRCCR_LATITUDE, <<"(-?\\d+\\.\\d+),">>). %% Latitude
  --define(TRCCR_LONGITUDE, <<"(-?\\d+\\.\\d+),">>). %% Longitude
  --define(TRCCR_SPEED, <<"(\\d+\\.\\d+),">>). %% Speed
  --define(TRCCR_COURSE, <<"(\\d+\\.\\d+),">>). %% Course
  --define(TRCCR_ALTITUDE, <<"(-?\\d+\\.\\d+),">>). %% Altitude
  --define(TRCCR_BATTERY, <<"(\\d+\\.?\\d*),">>). %% Battery
  --define(TRCCR_ANY, <<".+">>). %%
  -
  --define(TRCCR_PATTERN,
  -  <<
  -  ?TRCCR_HEADER/binary,
  -  ?TRCCR_DATE/binary,
  -  ?TRCCR_TIME/binary,
  -  ?TRCCR_VALIDITY/binary,
  -  ?TRCCR_LATITUDE/binary,
  -  ?TRCCR_LONGITUDE/binary,
  -  ?TRCCR_SPEED/binary,
  -  ?TRCCR_COURSE/binary,
  -  ?TRCCR_ALTITUDE/binary,
  -  ?TRCCR_BATTERY/binary,
  -  ?TRCCR_ANY/binary
  -  >>
  -).
  -
  --record(state, {protocol, transport, socket, timeout, device}).
  -
  +%% gen_server callbacks
  +-export([init/1,
  +  handle_call/3,
  +  handle_cast/2,
  +  handle_info/2,
  +  terminate/2,
  +  code_change/3]).
  +
  +
  +-ifdef(TEST).
  +-compile(export_all).
  +-endif. % EXPORT_ALL
  +
  +-define(TIMEOUT, infinity).
  +
  +-define(SERVER, ?MODULE).
  +
  +%% PATTERN_GPRMC: \$GPRMC,(\d{2})(\d{2})(\d{2})\.?\d*,([AV]),(\d{2})(\d{2}\.\d+),([NS]),(\d{2,3})(\d{2}\.\d+),([EW]),(\d+\.?\d*)?,(\d+\.?\d*)?,(\d{2})(\d{2})(\d{2}).*
  +%% PATTERN_GPGGA: \$GPGGA,(\d{2})(\d{2})(\d{2})\.?\d*,(\d+)(\d{2}\.\d+),([NS]),(\d+)(\d{2}\.\d+),([EW]),.*
  +%% PATTERN_GPRMA: \$GPRMA,([AV]),(\d{2})(\d{2}\.\d+),([NS]),(\d{3})(\d{2}\.\d+),([EW]),,,(\d+\.?\d*)?,(\d+\.?\d*)?,.*
  +%% PATTERN_TRCCR: \$TRCCR,(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.?\d*,([AV]),(-?\d+\.\d+),(-?\d+\.\d+),(\d+\.\d+),(\d+\.\d+),(-?\d+\.\d+),(\d+\.?\d*),.*
  +
  +-define(PATTERN_PGID, list_to_binary([
  +    "\\$PGID," ++
  +    "(\\d*)" ++           %% imei
  +    "\\*" ++
  +    "([0-9,a-f,A-F]{2})" ++  %% crc
  +    ".*"
  +])).
  +
  +-define(PATTERN_GPRMC, list_to_binary([
  +    "\\$GPRMC," ++
  +    "(\\d{2})(\\d{2})(\\d{2})\\.?\\d*," ++   %% time
  +    "([AV])," ++                        %% validity
  +    "(\\d{2})(\\d{2}\\.\\d+)," ++           %% latitude
  +    "([NS])," ++
  +    "(\\d{2,3})(\\d{2}\\.\\d+)," ++         %% longitude
  +    "([EW])," ++
  +    "(\\d+\\.?\\d*)?," ++                  %% speed
  +    "(\\d+\\.?\\d*)?," ++                  %% course
  +    "(\\d{2})(\\d{2})(\\d{2})" ++          %% date
  +    ".*"
  +])).
  +
  +-define(PATTERN_GPGGA, list_to_binary([
  +    "\\$GPGGA," ++
  +    "(\\d{2})(\\d{2})(\\d{2})\\.?\\d*," ++   %% time
  +    "(\\d+)(\\d{2}\\.\\d+)," ++             %% latitude
  +    "([NS])," ++
  +    "(\\d+)(\\d{2}\\.\\d+)," ++             %% longitude
  +    "([EW])," ++
  +    ".*"
  +])).
  +
  +-define(PATTERN_GPRMA, list_to_binary([
  +    "\\$GPRMA," ++
  +    "([AV])," ++                %% validity
  +    "(\\d{2})(\\d{2}\\.\\d+)," ++   %% latitude
  +    "([NS])," ++
  +    "(\\d{3})(\\d{2}\\.\\d+)," ++   %% longitude
  +    "([EW]),,," ++
  +    "(\\d+\\.?\\d*)?," ++          %% speed
  +    "(\\d+\\.?\\d*)?," ++          %% course
  +    ".*"
  +])).
  +
  +-define(PATTERN_TRCCR, list_to_binary([
  +    "\\$TRCCR," ++
  +    "(\\d{4})(\\d{2})(\\d{2})" ++          %% date
  +    "(\\d{2})(\\d{2})(\\d{2})\\.?\\d*," ++   %% time
  +    "([AV])," ++                        %% validity
  +    "(-?\\d+\\.\\d+)," ++                  %% latitude
  +    "(-?\\d+\\.\\d+)," ++                  %% longitude
  +    "(\\d+\\.\\d+)," ++                    %% speed
  +    "(\\d+\\.\\d+)," ++                    %% course
  +    "(-?\\d+\\.\\d+)," ++                  %% altitude
  +    "(\\d+\\.?\\d*)," ++                   %% battery
  +    ".*"
  +])).
  +
  +-define(SOCKET_OPTS, [{active, once}, {packet, line}]).
  +
  +-record(state, {protocol, transport, socket, timeout, deviceId = 0}).
  +
  +%%%===================================================================
  +%%% API
  +%%%===================================================================
  +
  +%%--------------------------------------------------------------------
  +%% @doc
  +%% Starts the server
  +%%
  +%% @end
  +%%--------------------------------------------------------------------
  +-spec(start_link(Ref :: any(), Socket :: any(), Transport :: any(), Opts :: any()) ->
  +  {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
   start_link(Ref, Socket, Transport, Opts) ->
  -  Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
  -  {ok, Pid}.
  -
  -init(Ref, Socket, Transport, Opts) ->
  +  {ok, proc_lib:spawn_link(?MODULE, init, [{Ref, Socket, Transport, Opts}])}.
  +
  +%%%===================================================================
  +%%% gen_server callbacks
  +%%%===================================================================
  +
  +%%--------------------------------------------------------------------
  +%% @private
  +%% @doc
  +%% Initializes the server
  +%%
  +%% @spec init(Args) -> {ok, State} |
  +%%                     {ok, State, Timeout} |
  +%%                     ignore |
  +%%                     {stop, Reason}
  +%% @end
  +%%--------------------------------------------------------------------
  +-spec(init(Args :: term()) ->
  +  {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
  +  {stop, Reason :: term()} | ignore).
  +init({Ref, Socket, Transport, Opts}) ->
     ok = ranch:accept_ack(Ref),
  +  ok = Transport:setopts(Socket, ?SOCKET_OPTS),
     Protocol = proplists:get_value(protocol, Opts),
  -  loop(#state{protocol = Protocol, socket = Socket, transport = Transport}).
  -
  -loop(State = #state{protocol = Protocol, socket = Socket, transport = Transport, device = Device}) ->
  -  case Transport:recv(Socket, 0, 5000) of
  -    {ok, Data} ->
  -      em_logger:info("[packet] unit: ip = '~s' data: ~s", [em_hardware:resolve(Socket), Data]),
  -      case parse(Data) of
  -        {<<"PGID">>, Imei} ->
  -          em_logger:info("[packet] unit: ip = '~s' imei = '~s'", [em_hardware:resolve(Socket), Imei]),
  -          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} ->
  -                  loop(State#state{device = Object})
  -          end;
  -        {<<"PCPTI">>, Imei} ->
  -          em_logger:info("[packet] unit: ip = '~s' imei = '~s'", [em_hardware:resolve(Socket), Imei]),
  -          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} ->
  -                  loop(State#state{device = Object})
  -          end;
  -        {<<"GPFID">>, Imei} ->
  -          em_logger:info("[packet] unit: ip = '~s' imei = '~s'", [em_hardware:resolve(Socket), Imei]),
  -          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} ->
  -                  loop(State#state{device = Object})
  -          end;
  -        {<<"IMEI">>, Imei} ->
  -          em_logger:info("[packet] unit: ip = '~s' imei = '~s'", [em_hardware:resolve(Socket), Imei]),
  -          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} ->
  -                  loop(State#state{device = Object})
  -          end;
  -        {<<"GPRMC">>, {ok, PositionModel}} ->
  -          Position = PositionModel#position{
  -            deviceId = Device#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), Device#device.id, Device#device.uniqueId, Position]),
  -          em_data_manager:create_position(Device, Position),
  -          Transport:send(Socket, <<"OK1\r\n">>),
  -          loop(State);
  -        {<<"GPGGA">>, {ok, PositionModel}} ->
  -          Position = PositionModel#position{
  -            deviceId = Device#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), Device#device.id, Device#device.uniqueId, Position]),
  -          em_data_manager:create_position(Device, Position),
  -          Transport:send(Socket, <<"OK1\r\n">>),
  -          loop(State);
  -        {<<"GPRMA">>, {ok, PositionModel}} ->
  -          Position = PositionModel#position{
  -            deviceId = Device#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), Device#device.id, Device#device.uniqueId, Position]),
  -          em_data_manager:create_position(Device, Position),
  -          Transport:send(Socket, <<"OK1\r\n">>),
  -          loop(State);
  -        {<<"TRCCR">>, {ok, PositionModel}} ->
  -          Position = PositionModel#position{
  -            deviceId = Device#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), Device#device.id, Device#device.uniqueId, Position]),
  -          em_data_manager:create_position(Device, Position),
  -          Transport:send(Socket, <<"OK1\r\n">>),
  -          loop(State);
  -        _ ->
  -          Transport:close(Socket)
  +  gen_server:enter_loop(?MODULE, [],
  +    #state{protocol = Protocol, socket = Socket, transport = Transport}, ?TIMEOUT).
  +%%{ok, #state{}}.
  +
  +%%--------------------------------------------------------------------
  +%% @private
  +%% @doc
  +%% Handling call messages
  +%%
  +%% @end
  +%%--------------------------------------------------------------------
  +-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
  +    State :: #state{}) ->
  +  {reply, Reply :: term(), NewState :: #state{}} |
  +  {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
  +  {noreply, NewState :: #state{}} |
  +  {noreply, NewState :: #state{}, timeout() | hibernate} |
  +  {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
  +  {stop, Reason :: term(), NewState :: #state{}}).
  +handle_call(_Request, _From, State) ->
  +  {reply, ok, State}.
  +
  +%%--------------------------------------------------------------------
  +%% @private
  +%% @doc
  +%% Handling cast messages
  +%%
  +%% @end
  +%%--------------------------------------------------------------------
  +-spec(handle_cast(Request :: term(), State :: #state{}) ->
  +  {noreply, NewState :: #state{}} |
  +  {noreply, NewState :: #state{}, timeout() | hibernate} |
  +  {stop, Reason :: term(), NewState :: #state{}}).
  +handle_cast(_Request, State) ->
  +  {noreply, State}.
  +
  +%%--------------------------------------------------------------------
  +%% @private
  +%% @doc
  +%% Handling all non call/cast messages
  +%%
  +%% @spec handle_info(Info, State) -> {noreply, State} |
  +%%                                   {noreply, State, Timeout} |
  +%%                                   {stop, Reason, State}
  +%% @end
  +%%--------------------------------------------------------------------
  +-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
  +  {noreply, NewState :: #state{}} |
  +  {noreply, NewState :: #state{}, timeout() | hibernate} |
  +  {stop, Reason :: term(), NewState :: #state{}}).
  +handle_info({command, Command}, State) ->
  +  do_execute_command(State, Command);
  +handle_info({tcp, _, <<67,78,88,78,0,0,0,1,0,0,4,0,27,0,0,0,77,10>>}, State = #state{socket = Socket, transport = Transport}) ->
  +  Transport:setopts(Socket, ?SOCKET_OPTS),
  +  {noreply, State, ?TIMEOUT};
  +handle_info({tcp, Socket, Data}, State = #state{socket = Socket, transport = Transport}) when byte_size(Data) > 1 ->
  +  Transport:setopts(Socket, ?SOCKET_OPTS),
  +  do_process_data(State, Data);
  +handle_info({tcp_closed, _Socket}, State) ->
  +  {stop, normal, State};
  +handle_info({tcp_error, _, Reason}, State) ->
  +  {stop, Reason, State};
  +handle_info(timeout, State) ->
  +  {stop, normal, State};
  +handle_info(_Info, State) ->
  +  {stop, normal, State}.
  +%%handle_info(_Info, State) ->
  +%%  {noreply, State}.
  +
  +%%--------------------------------------------------------------------
  +%% @private
  +%% @doc
  +%% This function is called by a gen_server when it is about to
  +%% terminate. It should be the opposite of Module:init/1 and do any
  +%% necessary cleaning up. When it returns, the gen_server terminates
  +%% with Reason. The return value is ignored.
  +%%
  +%% @spec terminate(Reason, State) -> void()
  +%% @end
  +%%--------------------------------------------------------------------
  +-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
  +    State :: #state{}) -> term()).
  +terminate(_Reason, _State) ->
  +  ok.
  +
  +%%--------------------------------------------------------------------
  +%% @private
  +%% @doc
  +%% Convert process state when code is changed
  +%%
  +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
  +%% @end
  +%%--------------------------------------------------------------------
  +-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
  +    Extra :: term()) ->
  +  {ok, NewState :: #state{}} | {error, Reason :: term()}).
  +code_change(_OldVsn, State, _Extra) ->
  +  {ok, State}.
  +
  +%%%===================================================================
  +%%% Internal functions
  +%%%===================================================================
  +do_execute_command(State = #state{transport = Transport, socket = Socket}, Command) ->
  +  case em_manager_devices:get_by_id(Command#command.deviceId) of
  +    {ok, Device} ->
  +      case do_encode_command(Device#device.uniqueId, Command) of
  +        {ok, CommandBin} ->
  +          em_logger:info("CommandBin: ~s", [CommandBin]),
  +          Transport:send(Socket, CommandBin),
  +          {noreply, State, ?TIMEOUT};
  +        {error, Reason} ->
  +          em_logger:info("Error: ~s", [Reason]),
  +          {noreply, State, ?TIMEOUT}
         end;
  -    _ ->
  -      Transport:close(Socket)
  +    {error, Reason} ->
  +      em_logger:info("Error: ~s", [Reason]),
  +      {noreply, State, ?TIMEOUT}
     end.
  +
  +do_encode_command(_UniqueId, _Command) ->
  +  {error, <<"Unsupported command">>}.
  +
  +do_process_data(State = #state{transport = _Transport, socket = Socket, protocol = _Protocol, deviceId = 0}, Data) ->
  +  em_logger:info("[packet] # unit: ip = '~s' data: ~s", [em_hardware:resolve(Socket), Data]),
  +  case parse(Data) of
  +    {ok, Imei} ->
  +      em_logger:info("[packet] unit: ip = '~s' imei = '~s'", [em_hardware:resolve(Socket), Imei]),
  +      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]),
  +          {stop, normal, State};
  +        {ok, Object} ->
  +          em_proc:registry(Object#device.id, self()),
  +          {noreply, State#state{deviceId = Object#device.id}, ?TIMEOUT}
  +      end;
  +    {error, Message} ->
  +      em_logger:info("ERROR: ~s", [Message]),
  +      {stop, normal, State}
  +  end;
  +do_process_data(State = #state{transport = _Transport, socket = Socket, protocol = _Protocol, deviceId = DeviceId}, Data) ->
  +  em_logger:info("[packet] ## unit: ip = '~s' data: ~s", [em_hardware:resolve(Socket), Data]),
  +  case parse(Data) of
  +    {ok, PositionModel} ->
  +      do_save_position(State, DeviceId, PositionModel);
  +    {error, Message} ->
  +      em_logger:info("ERROR: ~s", [Message]),
  +      {stop, normal, State}
  +  end;
  +do_process_data(State, _) ->
  +  %%em_logger:info("ERROR: parsing packet"),
  +  {stop, normal, State}.
  +
  +do_save_position(State = #state{socket = Socket, protocol = Protocol}, DeviceId, PositionModel) ->
  +  em_logger:info("[packet] unit: ip = '~s' message: ~w", [em_hardware:resolve(Socket), PositionModel]),
  +  Position = PositionModel#position{
  +    deviceId = DeviceId,
  +    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' position: ~w", [em_hardware:resolve(Socket), DeviceId, Position]),
  +  em_data_manager:create_position(DeviceId, Position),
  +  {noreply, State, ?TIMEOUT}.
  +
   
   % echo "5. t55"
   % (echo -n -e "\$PGID,123456789012345*0F\r\n\$GPRMC,120500.000,A,6000.0000,N,13000.0000,E,0.00,0.00,010112,,*33\r\n";) | nc -v localhost 5005
  -parse(Data) ->
  -  case em_regexp:match(Data, ?PATTERN) of
  -    {match, [_, Header | _]} ->
  -      io:format("match packet: ~s~n", [Header]),
  -      case Header of
  -        <<"PGID">> ->
  -          {Header, parse_pgid(Data)};
  -        <<"PCPTI">> ->
  -          {Header, parse_pcpti(Data)};
  -        <<"GPFID">> ->
  -          {Header, parse_gpfid(Data)};
  -        <<"IMEI">> ->
  -          {Header, parse_imei(Data)};
  -        <<"GPRMC">> ->
  -          {Header, parse_gprmc(Data)};
  -        <<"GPGGA">> ->
  -          {Header, parse_gpgga(Data)};
  -        <<"GPRMA">> ->
  -          {Header, parse_gprma(Data)};
  -        <<"TRCCR">> ->
  -          {Header, parse_trccr(Data)};
  -        _ ->
  -         null
  -      end;
  -    nomatch ->
  -      null
  -  end.
  -
  -parse_pgid(Data) ->
  -  case em_regexp:match(Data, ?PGID_PATTERN) of
  -    {match, [_, Imei|_]} ->
  -      Imei;
  -    _ ->
  -      null
  -  end.
  -
  -parse_pcpti(Data) ->
  -  case em_regexp:match(Data, ?PCPTI_PATTERN) of
  -    {match, [_, Imei|_]} ->
  -      Imei;
  -    _ ->
  -      null
  -  end.
  -
  -parse_gpfid(Data) ->
  -  case em_regexp:match(Data, ?GPFID_PATTERN) of
  -    {match, [_, Imei|_]} ->
  -      Imei;
  -    _ ->
  -      null
  -  end.
  -
  -parse_imei(Data) ->
  -  case em_regexp:match(Data, ?IMEI_PATTERN) of
  -    {match, [_, Imei|_]} ->
  -      Imei;
  -    _ ->
  -      null
  -  end.
  -
  -parse_gprmc(Data) ->
  -  case em_regexp:match(Data, ?GPRMC_PATTERN) of
  -    {match, [_, Hour, Minute, Second, Validity, LatDD, LatMM_MMMM, LatType, LonDD, LonMM_MMMM, LonType, Speed, Course, Day, Month, Year|_]} ->
  -      {ok, #position{
  -        deviceTime = parse_date(Year, Month, Day, Hour, Minute, Second),
  +parse(Data = <<"\$PGID", _/binary>>) ->
  +  case em_regexp:match(Data, ?PATTERN_PGID) of
  +    {ok, [_, Imei | _]} ->
  +      {ok, Imei};
  +    Reason ->
  +      Reason
  +  end;
  +parse(Data = <<"\$GPRMC", _/binary>>) ->
  +  case em_regexp:match(Data, ?PATTERN_GPRMC) of
  +    {ok, [_, Hour, Minute, Second, Validity, LatDD, LatMM_MMMM, LatType, LonDD, LonMM_MMMM, LonType, Speed, Course, Day, Month, Year|_]} ->
  +      Position = #position{
  +        deviceTime = parse_date(Year + 2000, Month, Day, Hour, Minute, Second),
           latitude = parse_coord(LatDD, LatMM_MMMM, LatType),
           longitude = parse_coord(LonDD, LonMM_MMMM, LonType),
           speed = parse_speed(Speed),
           course = parse_course(Course),
  -        valid = parse_valid(Validity)
  -      }};
  +        valid = parse_valid(Validity),
  +        attributes = #{}
  +      },
  +      {ok, Position};
       Reason ->
  -      {error, Reason}
  -  end.
  -
  -
  -parse_gpgga(Data) ->
  -  case em_regexp:match(Data, ?GPGGA_PATTERN) of
  -    {match, [_, _Hour, _Minute, _Second, LatDD, LatMM_MMMM, LatType, LonDD, LonMM_MMMM, LonType|_]} ->
  -      {ok, #position{
  -        %%time => parse_date(Year, Month, Day, Hour, Minute, Second),
  +      Reason
  +  end;
  +parse(Data = <<"\$GPGGA", _/binary>>) ->
  +  case em_regexp:match(Data, ?PATTERN_GPGGA) of
  +    {ok, [_, _Hour, _Minute, _Second, LatDD, LatMM_MMMM, LatType, LonDD, LonMM_MMMM, LonType|_]} ->
  +      Position = #position{
  +        %%time => parse_date(Year + 2000, Month, Day, Hour, Minute, Second),
           latitude = parse_coord(LatDD, LatMM_MMMM, LatType),
  -        longitude = parse_coord(LonDD, LonMM_MMMM, LonType)
  -      }};
  +        longitude = parse_coord(LonDD, LonMM_MMMM, LonType),
  +        attributes = #{}
  +      },
  +      {ok, Position};
       Reason ->
  -      {error, Reason}
  -  end.
  -
  -parse_gprma(Data) ->
  -  case em_regexp:match(Data, ?GPRMA_PATTERN) of
  -    {match, [_, Validity, LatDD, LatMM_MMMM, LatType, LonDD, LonMM_MMMM, LonType, Speed, Course|_]} ->
  -      {ok, #position{
  -        %%time => parse_date(Year, Month, Day, Hour, Minute, Second),
  +      Reason
  +  end;
  +parse(Data = <<"\$GPRMA", _/binary>>) ->
  +  case em_regexp:match(Data, ?PATTERN_GPRMA) of
  +    {ok, [_, Validity, LatDD, LatMM_MMMM, LatType, LonDD, LonMM_MMMM, LonType, Speed, Course|_]} ->
  +      Position = #position{
  +        %%time => parse_date(Year + 2000, Month, Day, Hour, Minute, Second),
           latitude = parse_coord(LatDD, LatMM_MMMM, LatType),
           longitude = parse_coord(LonDD, LonMM_MMMM, LonType),
           speed = parse_speed(Speed),
           course = parse_course(Course),
  -        valid = parse_valid(Validity)
  -      }};
  +        valid = parse_valid(Validity),
  +        attributes = #{}
  +      },
  +      {ok, Position};
       Reason ->
  -      {error, Reason}
  -  end.
  -
  -parse_trccr(Data) ->
  -    %% $TRCCR,20150727040136.279,A,53.897743,27.442885,0.00,0.00,0.00,34,*34
  -  case em_regexp:match(Data, ?TRCCR_PATTERN) of
  -    {match, [_, Year, Month, Day, Hour, Minute, Second, Validity, Lat, Lon, Speed, Course, Altitude, Battery|_]} ->
  -      {ok, #position{
  -        deviceTime = trccr_parse_date(Year, Month, Day, Hour, Minute, Second),
  +      Reason
  +  end;
  +parse(Data = <<"\$TRCCR", _/binary>>) ->
  +  case em_regexp:match(Data, ?PATTERN_TRCCR) of
  +    {ok, [_, Year, Month, Day, Hour, Minute, Second, Validity, Lat, Lon, Speed, Course, Altitude, Battery|_]} ->
  +      Position = #position{
  +        deviceTime = parse_date(Year, Month, Day, Hour, Minute, Second),
           latitude = list_to_float(binary_to_list(Lat)),
           longitude = list_to_float(binary_to_list(Lon)),
           speed = parse_speed(Speed),
  
positive.code:   false.
  
  parse_date(Year, Month, Day, Hour, Minute, Second) ->
    Date = {
      {
        list_to_integer(binary_to_list(Year)) + 2000,
        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).
  
  
  trccr_parse_date(Year, Month, Day, Hour, Minute, Second) ->
    Date = {
      {
        list_to_integer(binary_to_list(Year)),
  
negative.code:   em_helper_time:datetime_to_utc(Date).
  
  bin_to_num(Bin) ->
    N = binary_to_list(Bin),
    case string:to_float(N) of
      {error, no_float} ->
        list_to_integer(N);
      {F, _Rest} ->
        F
    end.
  
  %% "\$PGID,123456789012345*0F\r\n\$TRCCR,20150727040136.279,A,53.897743,27.442885,0.00,0.00,0.00,34,*34\r\n
  test() ->
    %%Packet0 = <<"\$PGID,123456789012345*0F\r\n">>,
    Packet1 = <<"\$TRCCR,20150727040136.279,A,53.897743,27.442885,0.00,0.00,0.00,34,*34\r\n">>,
    parse(Packet1).
    %%parse(Packet1).