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.
Summary
Functions
The type itself, if it's one; raises ArgumentError if it isn't, as for a member that doesn't
fit in its structure.
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.
A value from its bytes, or :error if there aren't as many as the type takes.
A value as bytes; raises ArgumentError if it doesn't fit the type.
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.
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.
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.
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.
The size of a type's values in bytes.
Types
@type member() :: {String.t(), non_neg_integer(), t()}
A member of a structure: its name, offset in bytes (in bits for :bit), and type.
@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()}
Functions
The type itself, if it's one; raises ArgumentError if it isn't, as for a member that doesn't
fit in its structure.
@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}]}
A value from its bytes, or :error if there aren't as many as the type takes.
A value as bytes; raises ArgumentError if it doesn't fit the type.
@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
@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.
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}]}
@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
@spec size(t()) :: non_neg_integer()
The size of a type's values in bytes.