# `Sidereon.GNSS.Navigation.LNAV`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/gnss/navigation/lnav.ex#L1)

GPS L1 C/A LNAV navigation message synthesis and decoding (subframes 1-3).

The legacy navigation (LNAV) message is the data stream modulated onto the
GPS L1 C/A signal at 50 bits per second. Its structure is defined in
IS-GPS-200 (Section 20.3): the message is organized into 1500-bit *frames*,
each frame being five 300-bit *subframes*, and each subframe being ten 30-bit
*words*. Every word carries 24 source data bits (most significant first)
followed by 6 parity bits.

This module covers the clock and ephemeris subframes:

  * Subframe 1 - SV clock correction and health (IS-GPS-200 Table 20-I).
  * Subframe 2 - first half of the ephemeris (IS-GPS-200 Table 20-II).
  * Subframe 3 - second half of the ephemeris (IS-GPS-200 Table 20-III).

The first word of every subframe is the telemetry (TLM) word; the second is
the hand-over word (HOW). Both are described in IS-GPS-200 Section 20.3.3.

## Words and bits

A subframe is represented as a flat list of 300 bits (`0`/`1`), most
significant bit first, with the ten words concatenated in transmission
order. `word_length/0` is 30 and `subframe_length/0` is 300.

## Parameters and units

`encode/2` and `decode/1` exchange an `Sidereon.GNSS.Navigation.LNAV.Ephemeris` struct
whose fields hold the natural engineering-unit values (the products of the
transmitted integers and their IS-GPS-200 scale factors). See that struct's
documentation for the per-field units. Angular ephemeris quantities use
*semicircles* (and semicircles/second), the harmonic correction terms use
radians, distances use meters, and clock/time quantities use seconds, exactly
as tabulated in IS-GPS-200.

## Parity

The 6 parity bits of each word are produced by the (32, 26) Hamming code of
IS-GPS-200 Section 20.3.5.2 and Table 20-XIV, including the rule that the two
trailing parity bits of the previous word (`D29*`, `D30*`) feed the current
word and that `D30*` complements the 24 transmitted data bits. The last two
data bits of the HOW and of word 10 are solved so that those words'
`D29`/`D30` parity bits are zero, per IS-GPS-200 Section 20.3.3.2. At the
start of each subframe the previous parity bits are seeded to zero, producing
self-consistent stand-alone subframes.

The bit packing, parity, scaling, and range validation are implemented in the
Rust core (`sidereon_core::navigation::lnav`); this module marshals the
parameter struct across the NIF and maps the result back to the public shapes.

## Examples

    iex> Sidereon.GNSS.Navigation.LNAV.word_length()
    30

    iex> Sidereon.GNSS.Navigation.LNAV.subframe_length()
    300

    iex> Sidereon.GNSS.Navigation.LNAV.preamble()
    139

# `decode`

```elixir
@spec decode(%{
  required(1) =&gt; [0 | 1],
  required(2) =&gt; [0 | 1],
  required(3) =&gt; [0 | 1]
}) ::
  {:ok, Sidereon.GNSS.Navigation.LNAV.Ephemeris.t()} | {:error, term()}
```

Decodes LNAV subframes 1-3 back into the engineering-unit parameter struct.

Accepts `%{1 => bits, 2 => bits, 3 => bits}` of 300-bit subframes. Parity is
verified on all 30 words first; a failure returns
`{:error, {:parity_failed, subframe, word}}` (word is 1-based). On success
returns `{:ok, %Sidereon.GNSS.Navigation.LNAV.Ephemeris{}}`.

# `encode`

```elixir
@spec encode(
  Sidereon.GNSS.Navigation.LNAV.Ephemeris.t(),
  keyword()
) ::
  {:ok,
   %{required(1) =&gt; [0 | 1], required(2) =&gt; [0 | 1], required(3) =&gt; [0 | 1]}}
  | {:error, term()}
```

Encodes clock and ephemeris parameters into LNAV subframes 1-3.

Returns `{:ok, %{1 => bits, 2 => bits, 3 => bits}}` where each value is a flat
list of 300 bits (most significant first). Out-of-range parameters yield
`{:error, {:out_of_range, field, value}}`; this function never raises on bad
input.

## Options

  * `:tow` - the 17-bit time-of-week count placed in each HOW (0..131071,
    default 0).
  * `:alert` - HOW alert flag (`0`/`1`, default 0).
  * `:anti_spoof` - HOW anti-spoof flag (`0`/`1`, default 0).
  * `:integrity` - TLM integrity status flag (`0`/`1`, default 0).
  * `:tlm_message` - 14-bit TLM message field (default 0).

# `parity`

```elixir
@spec parity([0 | 1], 0 | 1, 0 | 1) :: [0 | 1]
```

Computes the 6 parity bits of a word (IS-GPS-200 Table 20-XIV).

`data24` is the list of 24 *source* data bits (most significant first, before
the `D30*` complementation). `d29_prev` and `d30_prev` are the two trailing
parity bits of the previous word. Returns `[D25, D26, D27, D28, D29, D30]`.

# `parity_valid?`

```elixir
@spec parity_valid?([0 | 1], 0 | 1, 0 | 1) :: boolean()
```

Verifies the parity of a single 30-bit word.

`word30` is the 30-bit word as transmitted (data bits possibly complemented
by `D30*`, followed by 6 received parity bits). `d29_prev`/`d30_prev` are the
previous word's trailing parity bits. Returns `true` when the recomputed
parity matches the received parity.

# `preamble`

```elixir
@spec preamble() :: 139
```

The 8-bit TLM preamble `1000 1011` as an integer (IS-GPS-200 Section 20.3.3.1).

## Examples

    iex> Sidereon.GNSS.Navigation.LNAV.preamble()
    139

# `subframe_id`

```elixir
@spec subframe_id([0 | 1]) :: {:ok, 1..5} | {:error, :bad_length}
```

Extracts the 3-bit subframe ID from a hand-over word.

Accepts a 30-bit HOW word or a full 300-bit subframe. Returns the subframe
identifier carried in HOW bits 20-22 (IS-GPS-200 Section 20.3.3.2).

## Examples

    iex> {:ok, sfs} = Sidereon.GNSS.Navigation.LNAV.encode(Sidereon.GNSS.Navigation.LNAV.Ephemeris.example(), tow: 0)
    iex> Sidereon.GNSS.Navigation.LNAV.subframe_id(sfs[2])
    {:ok, 2}

# `subframe_id!`

```elixir
@spec subframe_id!([0 | 1]) :: 1..5
```

Like `subframe_id/1` but raises on malformed input length.

# `subframe_length`

```elixir
@spec subframe_length() :: 300
```

Bit length of a single LNAV subframe (IS-GPS-200 Section 20.3.2).

## Examples

    iex> Sidereon.GNSS.Navigation.LNAV.subframe_length()
    300

# `tow`

```elixir
@spec tow([0 | 1]) :: {:ok, non_neg_integer()} | {:error, :bad_length}
```

Extracts the 17-bit time-of-week count from a hand-over word.

Accepts either a 30-bit HOW word or a full 300-bit subframe (whose word 2 is
the HOW). The returned value is the truncated Z-count carried in the HOW
(units of 6 seconds), per IS-GPS-200 Section 20.3.3.2.

## Examples

    iex> {:ok, sfs} = Sidereon.GNSS.Navigation.LNAV.encode(Sidereon.GNSS.Navigation.LNAV.Ephemeris.example(), tow: 12345)
    iex> Sidereon.GNSS.Navigation.LNAV.tow(sfs[1])
    {:ok, 12345}

# `tow!`

```elixir
@spec tow!([0 | 1]) :: non_neg_integer()
```

Like `tow/1` but raises on malformed input length.

# `word_length`

```elixir
@spec word_length() :: 30
```

Bit length of a single LNAV word (IS-GPS-200 Section 20.3.2).

## Examples

    iex> Sidereon.GNSS.Navigation.LNAV.word_length()
    30

---

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