# `ADS.DataType`
[🔗](https://github.com/tallakt/yaads/blob/v0.1.0/lib/ads/data_type.ex#L1)

A PLC's description of one of its data types: a structure and its members, an array, an
enumeration and its values, or another name for a type.

`ADS.Client.data_type/3` asks for one by name (index group `0xF011`), and
`ADS.Client.data_types/2` uploads all of them (`0xF00E`, their size from `0xF00F`). Each is an
entry of variable length, whose members are entries of their own inside it:

| Bytes | Field |
|---|---|
| 4 | the entry's length, padding included |
| 4 | version, 1 |
| 4, 4 | hash values |
| 4 | size in bytes |
| 4 | offset, of a member in its structure |
| 4 | ADS data type id (see `ADS.Type.id_name/1`) |
| 4 | flags |
| 2, 2, 2 | lengths of the name, the type and the comment |
| 2 | dimensions of an array |
| 2 | members of a structure |
| ... | the name, the type and the comment, each with a zero after it |
| 8 each | for each dimension, its first index and its number of elements |
| ... | each member, an entry of its own |
| 16 | the type's GUID, if flagged (`0x0080`) |
| size | a copy mask, if flagged (`0x0200`) |
| ... | methods, if flagged (`0x0800`): a count of 2 bytes, then entries of their own length |
| ... | attributes, if flagged (`0x1000`), as a symbol's (see `ADS.Symbol`) |
| ... | enumeration values, if flagged (`0x2000`): a count of 2 bytes, then each with a byte for the length of its name, the name and a zero, and the value in as many bytes as the type |

Beckhoff's manuals don't describe this layout either; it's written here from general knowledge
of the format, not from another implementation's code, and is yet to be checked against a PLC.
What follows the members is read as far as it makes sense, and left out after that. In a
structure, the offset and size of a BIT member (flagged `0x0020`) count bits.

`to_type/2` makes an `ADS.Type` of a description, for reading and writing its values.

# `t`

```elixir
@type t() :: %ADS.DataType{
  array: [{integer(), non_neg_integer()}],
  attributes: %{required(String.t()) =&gt; String.t()},
  comment: String.t(),
  data_type: atom() | non_neg_integer(),
  enum: [{String.t(), integer()}],
  flags: non_neg_integer(),
  guid: String.t() | nil,
  hash: non_neg_integer(),
  members: [t()],
  name: String.t(),
  offset: non_neg_integer(),
  size: non_neg_integer(),
  type: String.t(),
  type_hash: non_neg_integer(),
  version: non_neg_integer()
}
```

# `decode`

```elixir
@spec decode(binary(), ADS.Codepage.t()) :: {:ok, t()} | :error
```

The data type entry at the start of the bytes, as in the answer to a request by name; its
strings in UTF-8 if they are, as plain ASCII is, or else in the code page (see `ADS.Codepage`).

# `decode_all`

```elixir
@spec decode_all(binary(), ADS.Codepage.t()) :: {:ok, [t()]} | :error
```

Every data type entry in the bytes, as uploaded; the strings as for `decode/2`.

# `to_type`

```elixir
@spec to_type(t(), (String.t(), non_neg_integer() -&gt; {:ok, ADS.Type.t()} | term())) ::
  ADS.Type.t()
```

The `ADS.Type` of a data type. `resolve` gives the type of a name the description refers to,
with the size it must have, as `{:ok, type}`, or anything else when it can't; `ADS.Client` asks
the PLC. What can't be made out, such as a member whose type is unknown, is `{:raw, size}`: its
bytes, so that the rest still decode.

    iex> pump = %ADS.DataType{name: "ST_Pump", size: 16, data_type: :big_type, members: [
    ...>   %ADS.DataType{name: "nSpeed", type: "INT", size: 2, offset: 0, data_type: :int16},
    ...>   %ADS.DataType{name: "fTemp", type: "LREAL", size: 8, offset: 8, data_type: :real64}]}
    iex> ADS.DataType.to_type(pump, fn _name, _size -> :error end)
    {:struct, 16, [{"nSpeed", 0, :int}, {"fTemp", 8, :lreal}]}

---

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