# `EtherCAT.Datagram`
[🔗](https://github.com/tallakt/yaethercat/blob/v0.1.0/lib/ethercat/datagram.ex#L1)

An EtherCAT PDU, called a datagram here: one command to the SubDevices, with its address, its data
and the working counter they increment (ETG.1000.4, 5.4).

A datagram to send is given as a tuple, named for its command:

| Request | Does |
|---|---|
| `{:aprd, position, register, length}` | reads at the SubDevice at that position from the MainDevice, 0 the first |
| `{:apwr, position, register, data}` | writes there |
| `{:aprw, position, register, data}` | writes there and reads what was there |
| `{:fprd, station, register, length}` | reads at the SubDevice with that configured station address or alias |
| `{:fpwr, station, register, data}` | writes there |
| `{:fprw, station, register, data}` | writes there and reads what was there |
| `{:brd, register, length}` | reads at every SubDevice, the bitwise or of them all |
| `{:bwr, register, data}` | writes at every SubDevice |
| `{:brw, register, data}` | writes at every SubDevice and reads the bitwise or |
| `{:lrd, address, length}` | reads in the logical memory that the FMMUs map |
| `{:lwr, address, data}` | writes there |
| `{:lrw, address, data}` | writes there and reads, each FMMU doing what it is set up for |
| `{:armw, position, register, length}` | reads at one SubDevice and writes what it read at all the others after it |
| `{:frmw, station, register, length}` | likewise, by station address |

A register is an address in a SubDevice's own memory, 0 to 65535, its registers and the RAM behind
them alike. Lengths are in bytes, and multi-byte values are little-endian, as everything in
EtherCAT.

Each SubDevice that does what a datagram asks increments its working counter: by one for a read,
by one for a write, and by three for a read and write that does both (two for the write).

    iex> datagram = EtherCAT.Datagram.new({:fprd, 0x1001, 0x0130, 2})
    iex> datagram.address
    {0x1001, 0x0130}
    iex> datagram.data
    <<0, 0>>

# `command`

```elixir
@type command() ::
  :aprd
  | :apwr
  | :aprw
  | :fprd
  | :fpwr
  | :fprw
  | :brd
  | :bwr
  | :brw
  | :lrd
  | :lwr
  | :lrw
  | :armw
  | :frmw
```

The name of an EtherCAT command.

# `request`

```elixir
@type request() ::
  {:aprd | :fprd | :armw | :frmw, 0..65535, 0..65535, pos_integer()}
  | {:apwr | :aprw | :fpwr | :fprw, 0..65535, 0..65535, binary()}
  | {:brd, 0..65535, pos_integer()}
  | {:bwr | :brw, 0..65535, binary()}
  | {:lrd, 0..4_294_967_295, pos_integer()}
  | {:lwr | :lrw, 0..4_294_967_295, binary()}
```

A datagram to send, as in the table above.

# `t`

```elixir
@type t() :: %EtherCAT.Datagram{
  address: {0..65535, 0..65535} | 0..4_294_967_295,
  circulating: boolean(),
  command: command(),
  data: binary(),
  index: 0..255,
  irq: 0..65535,
  wkc: 0..65535
}
```

A datagram as it is on the wire. `address` is `{adp, ado}` for the physical commands, the position
or station address and the register, as the spec calls them, and a 32-bit logical address for
`:lrd`, `:lwr` and `:lrw`. A position is sent as its negative, which each SubDevice increments on
the way, so `adp` is that in a datagram that came back.

# `auto_increment?`

```elixir
@spec auto_increment?(command()) :: boolean()
```

Whether a command addresses a SubDevice by its position.

# `decode_all`

```elixir
@spec decode_all(binary()) ::
  {:ok, [t()]} | {:error, :invalid_command | :invalid_length}
```

The datagrams of a frame, after its header, in order, or why they aren't: `:invalid_command` for a
command the spec doesn't define, and `:invalid_length` when they run past the end, or end before
it.

    iex> EtherCAT.Datagram.decode_all(<<0x07, 7, 3, 0, 0, 0, 1, 0, 0, 0, 0x11, 3, 0>>)
    {:ok, [%EtherCAT.Datagram{command: :brd, index: 7, address: {3, 0}, data: <<0x11>>, wkc: 3}]}

# `encode`

```elixir
@spec encode(t(), boolean()) :: binary()
```

A datagram on the wire, with `next` set when another follows it in the frame.

    iex> EtherCAT.Datagram.encode(%{EtherCAT.Datagram.new({:brd, 0x0000, 1}) | index: 7}, false)
    <<0x07, 7, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0>>

# `logical?`

```elixir
@spec logical?(command()) :: boolean()
```

Whether a command addresses the logical memory, rather than a SubDevice's own.

# `new`

```elixir
@spec new(request()) :: t()
```

A request as a datagram to send, with index 0. A read is sent with zeros, which a broadcast read ors
with what it reads. Raises `ArgumentError` for a request that isn't one, or whose data can't fit an
Ethernet frame.

# `size`

```elixir
@spec size(t()) :: pos_integer()
```

The bytes a datagram takes in a frame: its data and twelve.

---

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