# `TeltonikaCodec`
[🔗](https://github.com/neilberkman/teltonika_codec/blob/v0.1.0/lib/teltonika_codec.ex#L1)

Teltonika GPS tracker protocol parser for Elixir.

Parses the binary protocols used by Teltonika GPS tracking devices
(TAT141, FMB920, FMC130, TMT250, etc.) over TCP connections. Supports
Codec 8, Codec 8 Extended, and Codec 16.

## Quick Start

    # Parse an IMEI handshake
    {:ok, "352093085698206"} = TeltonikaCodec.parse_imei(data)

    # Parse a data packet (auto-detects codec)
    {:ok, records} = TeltonikaCodec.parse_packet(data)

## Protocol Overview

Teltonika devices communicate over TCP using a simple protocol:

1. Device connects and sends IMEI as `<<length::16, imei::binary>>`
2. Server responds with `0x01` (accept) or `0x00` (reject)
3. Device sends data packets containing AVL records
4. Server ACKs each packet with `<<record_count::32>>`
5. Repeat from step 3

## Supported Codecs

| Codec | ID | IO ID Size | IO Count Size | Variable IO | Generation Type |
|-------|------|------------|---------------|-------------|-----------------|
| 8     | 0x08 | 1 byte     | 1 byte        | No          | No              |
| 8 Ext | 0x8E | 2 bytes    | 2 bytes       | Yes (NX)    | No              |
| 16    | 0x10 | 2 bytes    | 1 byte        | No          | Yes             |

## Packet Structure

All codecs share the same outer frame:

    <<0::32, data_length::32, data::binary, crc::32>>

The `data` field starts with a codec ID byte, then N AVL records, then
a repeat of the record count for validation.

# `avl_record`

```elixir
@type avl_record() :: %{
  altitude: non_neg_integer(),
  heading: non_neg_integer(),
  io_elements: map(),
  latitude: float(),
  longitude: float(),
  priority: priority(),
  satellites: non_neg_integer(),
  speed: non_neg_integer(),
  timestamp: DateTime.t()
}
```

# `parse_result`

```elixir
@type parse_result() :: {:ok, [avl_record()]} | {:error, atom()}
```

# `priority`

```elixir
@type priority() :: :low | :high | :panic
```

# `ack`

```elixir
@spec ack(non_neg_integer()) :: binary()
```

Builds an ACK response for the device.

The server must respond with the number of accepted data records as a
4-byte big-endian integer. **Failure to ACK causes the device to
retransmit, draining battery.**

## Examples

    iex> TeltonikaCodec.ack(2)
    <<0, 0, 0, 2>>

# `check_packet`

```elixir
@spec check_packet(binary()) ::
  {:complete, binary(), binary()} | :incomplete | {:error, atom()}
```

Checks if a complete packet is available in the buffer.

Returns `{:complete, packet_binary, remaining_buffer}` if a full packet
is available, `:incomplete` if more data is needed, or `{:error, reason}`.

Useful for accumulating TCP data until a full frame arrives.

## Examples

    iex> packet = Base.decode16!("000000000000002808010000016B40D9AD80010000000000000000000000000000000103021503010101425E100000010000F22A")
    iex> {:complete, ^packet, <<>>} = TeltonikaCodec.check_packet(packet)

    iex> TeltonikaCodec.check_packet(<<0, 0, 0>>)
    :incomplete

# `imei_accept`

```elixir
@spec imei_accept() :: binary()
```

Builds the IMEI accept response (`0x01`).

# `imei_reject`

```elixir
@spec imei_reject() :: binary()
```

Builds the IMEI reject response (`0x00`).

# `parse_imei`

```elixir
@spec parse_imei(binary()) :: {:ok, String.t()} | {:error, atom()}
```

Parses an IMEI handshake packet.

The device sends `<<length::16, imei::binary-size(length)>>` on connect.

## Examples

    iex> TeltonikaCodec.parse_imei(<<0, 15, "352093085698206">>)
    {:ok, "352093085698206"}

    iex> TeltonikaCodec.parse_imei(<<0, 5, "12345">>)
    {:error, :invalid_imei_format}

# `parse_packet`

```elixir
@spec parse_packet(binary()) :: parse_result()
```

Parses a complete Codec 8/8E/16 data packet, auto-detecting the codec.

Returns `{:ok, records}` where records is a list of AVL record maps.

## Examples

    iex> packet = Base.decode16!("000000000000002808010000016B40D9AD80010000000000000000000000000000000103021503010101425E100000010000F22A")
    iex> {:ok, [record]} = TeltonikaCodec.parse_packet(packet)
    iex> record.priority
    :high

---

*Consult [api-reference.md](api-reference.md) for complete listing*
