ADS.Command (yaads v0.1.0)

Copy Markdown View Source

The nine ADS commands: the data of their requests and of the responses to them, inside AMS packets (see ADS.AMS). All numbers are little-endian.

A request is a tuple, and its result is a value:

RequestCommandResult
:read_device_info1{:ok, %{name: name, version: {major, minor, build}}}
{:read, index_group, index_offset, length}2{:ok, binary}
{:write, index_group, index_offset, binary}3:ok
:read_state4{:ok, %{ads_state: state, device_state: integer}}
{:write_control, ads_state, device_state, binary}5:ok
{:add_notification, index_group, index_offset, length, mode, max_delay, cycle_time}6{:ok, handle}
{:delete_notification, handle}7:ok
{:read_write, index_group, index_offset, read_length, binary}9{:ok, binary}

Command 8, the device notification, goes the other way: a device sends it, unasked and unanswered, with the samples of the notifications a client added (decode_notification/1).

The mode of a notification is :cyclic, every cycle time, or :on_change, checked every cycle time; Beckhoff's documentation calls them ADSTRANS_SERVERCYCLE (3) and ADSTRANS_SERVERONCHA (4). Its max delay and cycle time are in units of 100 ns on the wire, as in the ADS-DLL's AdsNotificationAttrib and its samples, where a cycle time of 10000000 is a second; the ADS spec's table of the command says 1 ms, which the TwinCAT devices don't follow.

iex> ADS.Command.encode_request({:read, 0x4020, 0, 4})
{2, <<0x20, 0x40, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0>>}
iex> ADS.Command.decode_response({:read, 0x4020, 0, 4}, <<0::32, 4::32-little, 42::32-little>>)
{:ok, <<42, 0, 0, 0>>}
iex> ADS.Command.decode_response({:read, 0xF005, 7, 4}, <<0x10, 0x07, 0, 0>>)
{:error, :device_symbol_not_found}

Summary

Types

The samples of a device notification: a timestamp, and each notification's data.

Functions

The command id of a request: 1 to 9, as in the AMS header.

The stamps of a device notification, each with the samples taken at its time, or :error for data that isn't one. A sample of no data says that its handle is no longer valid: the device sends one such, once, when its variable goes away, as after an online change.

The result of a request from the data of its response. A result other than 0 is the error of that return code; a response that doesn't fit the request, such as a read that gives more than asked for, is {:error, {:invalid_response, data}}.

A device notification's data, from its stamps: the inverse of decode_notification/1, for tests and for those who play a device.

The command id and data of a request. Raises ArgumentError for one that doesn't fit the protocol, such as an index group past 32 bits.

A FILETIME, 100 ns intervals since 1601-01-01 in UTC, as a DateTime, or nil for one it can't hold.

A DateTime as a FILETIME.

Types

index_group()

@type index_group() :: ADS.index_group()

index_offset()

@type index_offset() :: ADS.index_offset()

mode()

@type mode() :: :cyclic | :on_change | 0..4_294_967_295

request()

@type request() ::
  :read_device_info
  | {:read, index_group(), index_offset(), 0..4_294_967_295}
  | {:write, index_group(), index_offset(), binary()}
  | :read_state
  | {:write_control, ADS.state(), 0..65535, binary()}
  | {:add_notification, index_group(), index_offset(), 0..4_294_967_295, mode(),
     0..4_294_967_295, 0..4_294_967_295}
  | {:delete_notification, 0..4_294_967_295}
  | {:read_write, index_group(), index_offset(), 0..4_294_967_295, binary()}

result()

@type result() :: :ok | {:ok, term()} | {:error, ADS.error()}

stamp()

@type stamp() :: {DateTime.t() | nil, [{handle :: 0..4_294_967_295, binary()}]}

The samples of a device notification: a timestamp, and each notification's data.

Functions

command_id(arg1)

@spec command_id(request() | :device_notification) :: 1..9

The command id of a request: 1 to 9, as in the AMS header.

decode_notification(arg1)

@spec decode_notification(binary()) :: {:ok, [stamp()]} | :error

The stamps of a device notification, each with the samples taken at its time, or :error for data that isn't one. A sample of no data says that its handle is no longer valid: the device sends one such, once, when its variable goes away, as after an online change.

The timestamps are FILETIMEs, in UTC, here a DateTime with microseconds, the last digit of its 100 ns dropped; or nil for one past what DateTime can hold.

decode_response(arg1, data)

@spec decode_response(request(), binary()) :: result()

The result of a request from the data of its response. A result other than 0 is the error of that return code; a response that doesn't fit the request, such as a read that gives more than asked for, is {:error, {:invalid_response, data}}.

encode_notification(stamps)

@spec encode_notification([stamp()]) :: binary()

A device notification's data, from its stamps: the inverse of decode_notification/1, for tests and for those who play a device.

encode_request(request)

@spec encode_request(request()) :: {1..9, binary()}

The command id and data of a request. Raises ArgumentError for one that doesn't fit the protocol, such as an index group past 32 bits.

from_filetime(time)

@spec from_filetime(non_neg_integer()) :: DateTime.t() | nil

A FILETIME, 100 ns intervals since 1601-01-01 in UTC, as a DateTime, or nil for one it can't hold.

iex> ADS.Command.from_filetime(116_444_736_000_000_000)
~U[1970-01-01 00:00:00.000000Z]

to_filetime(datetime)

@spec to_filetime(DateTime.t() | nil) :: non_neg_integer()

A DateTime as a FILETIME.