ADS.DataType (yaads v0.1.0)

Copy Markdown View Source

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:

BytesField
4the entry's length, padding included
4version, 1
4, 4hash values
4size in bytes
4offset, of a member in its structure
4ADS data type id (see ADS.Type.id_name/1)
4flags
2, 2, 2lengths of the name, the type and the comment
2dimensions of an array
2members of a structure
...the name, the type and the comment, each with a zero after it
8 eachfor each dimension, its first index and its number of elements
...each member, an entry of its own
16the type's GUID, if flagged (0x0080)
sizea 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.

Summary

Functions

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).

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

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.

Types

t()

@type t() :: %ADS.DataType{
  array: [{integer(), non_neg_integer()}],
  attributes: %{required(String.t()) => 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()
}

Functions

decode(bytes, codepage \\ ADS.Codepage.default())

@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(bytes, codepage \\ ADS.Codepage.default())

@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(t, resolve)

@spec to_type(t(), (String.t(), non_neg_integer() -> {: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}]}