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

The data types of a TwinCAT PLC, and their values as Elixir terms.

| PLC | Type | Value |
|---|---|---|
| BOOL | `:bool` | `true` or `false` |
| BYTE, WORD, DWORD, LWORD | `:byte`, `:word`, `:dword`, `:lword` | an integer |
| SINT, USINT, INT, UINT, DINT, UDINT, LINT, ULINT | `:sint`, `:usint`, `:int`, ... `:ulint` | an integer |
| REAL, LREAL | `:real`, `:lreal` | a float, or `:nan`, `:infinity`, `:neg_infinity` |
| STRING(n) | `{:string, n}`, `{:string, n, :utf8}`, or `{:string, n, codepage}` | a string |
| WSTRING(n) | `{:wstring, n}` | a string |
| TIME, LTIME | `:time`, `:ltime` | an integer of ms, or of ns |
| TIME_OF_DAY, LTIME_OF_DAY | `:time_of_day`, `:ltime_of_day` | a `Time` |
| DATE, LDATE | `:date`, `:ldate` | a `Date` |
| DATE_AND_TIME, LDATE_AND_TIME | `:date_and_time`, `:ldate_and_time` | a `NaiveDateTime` |
| ARRAY [a..b, c..d] OF T | `{:array, t, [{a, b}, {c, d}]}` | a list, nested for each dimension |
| STRUCT, FUNCTION_BLOCK | `{:struct, size, [{name, offset, t}]}` | a map of the members' names to their values |
| UNION | `{:union, size, [{name, offset, t}]}` | a map of every member; one at a time when written |
| an enumeration | `{:enum, t, [{name, value}]}` | the name of the value, or its number if it has none |
| BIT, in a structure | `:bit` | `true` or `false` |
| anything else | `{:raw, size}` | its bytes |

    iex> ADS.Type.encode(:int, -2)
    <<254, 255>>
    iex> ADS.Type.decode({:string, 10}, "Pump 1" <> <<0::40>>)
    {:ok, "Pump 1"}
    iex> ADS.Type.parse("ARRAY [1..3] OF LREAL")
    {:ok, {:array, :lreal, [{1, 3}]}}

Numbers are little-endian, as everything in ADS. A STRING(n) takes n + 1 bytes, the last for the
zero that ends it, and holds Windows-1252, as Beckhoff's own libraries read it, unless declared
with the attribute `'TcEncoding' := 'UTF-8'`, or of another code page, `{:string, n, codepage}`
(see `ADS.Codepage` and `codepage/2`); what comes out is UTF-8, always. A WSTRING(n) takes 2(n + 1), in UCS-2, which has no characters past U+FFFF. TIME counts ms and
LTIME ns; TIME_OF_DAY ms since midnight and LTIME_OF_DAY ns; DATE and DATE_AND_TIME seconds since
1970, and LDATE and LDATE_AND_TIME ns, as signed 64-bit numbers. A PLC's dates and times have no
time zone, so they are naive here; LDATE_AND_TIME and LTIME_OF_DAY keep microseconds, dropping
the last three digits of their ns. A time of day past midnight, which no PLC makes but bytes can
hold, is its number.

A structure's members, a union's and an array's elements are where the PLC puts them: given by
the PLC's description of the type, or by `layout/2`, which follows TwinCAT 3's rules for
alignment. In a structure, the offset of a `:bit` member counts bits, from the lowest bit of the
structure's first byte.

`encode/2` raises `ArgumentError` for a value that doesn't fit its type, such as 70000 as an INT
or a struct with a member missing. `decode/2` never raises for any bytes of the right size.

# `member`

```elixir
@type member() :: {String.t(), non_neg_integer(), t()}
```

A member of a structure: its name, offset in bytes (in bits for `:bit`), and type.

# `t`

```elixir
@type t() ::
  :bool
  | :bit
  | :byte
  | :word
  | :dword
  | :lword
  | :sint
  | :usint
  | :int
  | :uint
  | :dint
  | :udint
  | :lint
  | :ulint
  | :real
  | :lreal
  | :time
  | :ltime
  | :date
  | :date_and_time
  | :time_of_day
  | :ldate
  | :ldate_and_time
  | :ltime_of_day
  | {:string, non_neg_integer()}
  | {:string, non_neg_integer(), :utf8 | ADS.Codepage.t()}
  | {:wstring, non_neg_integer()}
  | {:array, t(), [{integer(), integer()}]}
  | {:struct, non_neg_integer(), [member()]}
  | {:union, non_neg_integer(), [member()]}
  | {:enum, t(), [{String.t(), integer()}]}
  | {:raw, non_neg_integer()}
```

# `check!`

```elixir
@spec check!(term()) :: t()
```

The type itself, if it's one; raises `ArgumentError` if it isn't, as for a member that doesn't
fit in its structure.

# `codepage`

```elixir
@spec codepage(t(), ADS.Codepage.t()) :: t()
```

The type with its STRINGs in the code page, those that aren't of one already, nor UTF-8: as
`ADS.Client` reads and writes the PLC's with the code page it has.

    iex> ADS.Type.codepage({:array, {:string, 10}, [{1, 2}]}, :windows_1251)
    {:array, {:string, 10, :windows_1251}, [{1, 2}]}

# `decode`

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

A value from its bytes, or `:error` if there aren't as many as the type takes.

# `encode`

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

A value as bytes; raises `ArgumentError` if it doesn't fit the type.

# `from_id`

```elixir
@spec from_id(atom() | non_neg_integer(), non_neg_integer()) :: t() | nil
```

The type of a value of `size` bytes by its ADS data type id alone, or nil: for when a type's name
says nothing parse/2 knows.

    iex> ADS.Type.from_id(:uint16, 2)
    :uint
    iex> ADS.Type.from_id(:string, 81)
    {:string, 80}
    iex> ADS.Type.from_id(:big_type, 12)
    nil

# `id_name`

```elixir
@spec id_name(non_neg_integer()) :: atom() | non_neg_integer()
```

The name of an ADS data type id, as a symbol or a data type describes itself, or the id if it
has none: `:int16` for 2, `:big_type` for 65, the id of structures and arrays.

# `layout`

```elixir
@spec layout([{String.t(), t()}], keyword()) :: t()
```

A structure of members, each `{name, type}`, placed by TwinCAT 3's rules for alignment: each
member at an offset that's a multiple of the size of its type, or of `:pack_mode` if that's
smaller; strings at any byte; consecutive BIT members together in bytes; and the structure as a
whole a multiple of its most aligned member in size, so that an array of them stays aligned.

  * `:pack_mode` - 8, TwinCAT 3's default, or 4, 2, 1 or 0 (none), as with the attribute
    `'pack_mode'` of the structure; TwinCAT 2 had 1 on x86

For a structure on the PLC, the description the PLC has of its type is surer.

    iex> ADS.Type.layout([{"fVar", :lreal}, {"nVar1", :dint}, {"nVar2", :sint}])
    {:struct, 16, [{"fVar", 0, :lreal}, {"nVar1", 8, :dint}, {"nVar2", 12, :sint}]}
    iex> ADS.Type.layout([{"nVar2", :sint}, {"fVar", :lreal}], pack_mode: 1)
    {:struct, 9, [{"nVar2", 0, :sint}, {"fVar", 1, :lreal}]}

# `parse`

```elixir
@spec parse(String.t(), non_neg_integer() | nil) :: {:ok, t()} | :error
```

The type of a PLC type name, for the types TwinCAT has built in, strings, arrays of them, and
subranges such as `INT (0..100)`; `:error` for any other, such as a structure, whose description
the PLC has. POINTER TO, REFERENCE TO, XINT and the like are as big as the target's addresses,
which `size`, the size of a value, tells.

    iex> ADS.Type.parse("STRING(80)")
    {:ok, {:string, 80}}
    iex> ADS.Type.parse("ARRAY [0..1, -1..1] OF INT")
    {:ok, {:array, :int, [{0, 1}, {-1, 1}]}}
    iex> ADS.Type.parse("POINTER TO REAL", 8)
    {:ok, :ulint}
    iex> ADS.Type.parse("ST_Pump")
    :error

# `size`

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

The size of a type's values in bytes.

---

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