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:
| Request | Command | Result |
|---|---|---|
:read_device_info | 1 | {: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_state | 4 | {: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
@type index_group() :: ADS.index_group()
@type index_offset() :: ADS.index_offset()
@type mode() :: :cyclic | :on_change | 0..4_294_967_295
@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()}
@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
@spec command_id(request() | :device_notification) :: 1..9
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 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.
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.
@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]
@spec to_filetime(DateTime.t() | nil) :: non_neg_integer()
A DateTime as a FILETIME.