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

Finding ADS devices on the network, and adding routes to them, over UDP port 48899, as
TwinCAT's own Broadcast Search and Add Route do:

    {:ok, [%{address: {10, 0, 0, 5}, net_id: "5.36.69.176.1.1"}]} = ADS.Discovery.search()
    {:ok, %{net_id: "5.36.69.176.1.1"}} = ADS.Discovery.info("10.0.0.5")
    :ok = ADS.Discovery.add_route("10.0.0.5", password: "1")

A PLC lets in only clients it has a route for: their AMS NetId, and the address they come from.
`add_route/2` adds one with the login of a user on the PLC, Administrator unless told otherwise,
for the NetId the client is to use, by default its address with `.1.1` as `ADS.Client` takes
it, so that a client can connect after without TwinCAT's tools. The password goes over the
network as it is, as with TwinCAT's tools; Secure ADS adds routes inside TLS instead (see
`ADS.Secure`).

Beckhoff's manuals say only that discovery goes over UDP port 48899. What goes in it follows
Beckhoff's own ADS library ([github.com/Beckhoff/ADS](https://github.com/Beckhoff/ADS), MIT
License), which asks a device for its NetId over it, and adds routes; deleting them follows
Apache PLC4X's description of the protocol ([plc4x.apache.org](https://plc4x.apache.org),
Apache License 2.0). Each datagram, request or answer, is:

| Bytes | Field |
|---|---|
| 4 | `0x71146603` |
| 4 | an invoke id, 0, which the answer has too |
| 4 | the service: 1 for a device's info, 6 to add a route, 7 to delete one; with `0x80000000` added in the answer |
| 6, 2 | an AMS NetId and port, the sender's: in a request to add a route, the route's NetId |
| 4 | the number of tags |
| ... | the tags, each a 2-byte id, the length of its value in 2 bytes, and the value |

To add or delete a route, the tags are the route's name (12), its NetId (7), the user (13), the
password (2) and the client's address (5), the strings with a zero after them, in Windows-1252
here as in Secure ADS; the answer's tag 1 is its result, a return code of 4 bytes. All numbers
are little-endian.

An answer is taken only from the address asked, with the service and invoke id of the request;
anything else that comes is passed over. Nothing sent is ever empty, which older TwinCAT
routers stopped at (Beckhoff's advisory 2019-004).

# `datagram`

```elixir
@type datagram() :: %{
  :service =&gt; 0..4_294_967_295,
  optional(:invoke_id) =&gt; 0..4_294_967_295,
  net_id: ADS.NetId.t(),
  port: ADS.ams_port(),
  tags: [{0..65535, binary()}]
}
```

A datagram, as `encode/1` takes it, its invoke id 0 if left out, and `decode/1` gives it.

# `device`

```elixir
@type device() :: %{
  address: :inet.ip4_address(),
  net_id: ADS.net_id(),
  name: String.t() | nil,
  version: {byte(), byte(), 0..65535} | nil,
  fingerprint: String.t() | nil,
  tags: [{0..65535, binary()}]
}
```

A device that answered: its IP address, AMS NetId, and the tags of its answer, `{id, value}`,
and of those, as TwinCAT 3.1 4026 has them:

  * `name` - its name, tag 5, the tag of a computer's name in a route
  * `version` - TwinCAT's, tag 3, such as `{3, 1, 4026}`
  * `fingerprint` - the SHA-256 of its certificate for Secure ADS, tag 18, in hex as TwinCAT
    shows it and `secure: [fingerprint: ...]` takes it. It comes over UDP, which anyone on the
    network can answer: compare it with the one TwinCAT shows before trusting it.

Each is `nil` if the device doesn't give it.

# `add_route`

```elixir
@spec add_route(String.t() | :inet.ip4_address(), keyword()) :: :ok | {:error, term()}
```

Adds a route on a device, for a client to connect with.

  * `:password` - of the user on the device (required)
  * `:user` - (default `"Administrator"`)
  * `:net_id` - the client's NetId, as it's to start with `local_net_id:` if it's not the
    default: its address with `.1.1`
  * `:address` - the client's address, as the device is to know it (default the address it
    sends from)
  * `:name` - the route's name (default the address)
  * `:port` - the UDP port (default 48899)
  * `:timeout` - in ms (default 5000)
  * `:codepage` - of the strings (default Windows-1252; see `ADS.Codepage`)

`:ok` once the device has added the route, or `{:error, reason}`: an ADS return code if it
wouldn't, such as for a wrong password, or `:timeout`.

# `decode`

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

A datagram that came, or `:error` if it isn't one. Its tags are read as far as they go, and as
many as it says it has, at most.

# `delete_route`

```elixir
@spec delete_route(String.t() | :inet.ip4_address(), keyword()) ::
  :ok | {:error, term()}
```

Deletes a route on a device, with the login of a user on it, as `add_route/2` adds one and with
the same options. TwinCAT 3.1 4026 deletes the route of the name, by default the address, as
`add_route/2` names it: give the name it was added with. Another route with the same NetId stays.

`:ok` once the device has deleted it, or `{:error, reason}` as for `add_route/2`:
`:device_not_found` from Beckhoff's own system service for a route it hasn't. Beckhoff's router
lets a connection that's open stay, its route deleted. TwinCAT 3.1 4026's answer doesn't tell
whether there was such a route: it has answered `:ok`, and `:device_error`, to deleting one it
hadn't. So `mix ads.route --delete` sees that the device answers this machine over ADS before,
and not after, on a new connection.

# `encode`

```elixir
@spec encode(datagram()) :: binary()
```

A datagram as bytes to send.

# `info`

```elixir
@spec info(String.t() | :inet.ip4_address(), keyword()) ::
  {:ok, device()} | {:error, term()}
```

Asks one device for its info: its AMS NetId above all.

  * `:port` - the UDP port (default 48899)
  * `:timeout` - in ms (default 5000)
  * `:codepage` - of its name (default Windows-1252; see `ADS.Codepage`)

# `search`

```elixir
@spec search(keyword()) :: {:ok, [device()]} | {:error, :inet.posix()}
```

Asks every device on the network for its info, and gives those that answer in time, in the
order they did. A firewall on this machine that lets in only answers to what was sent, as ufw
does, drops theirs, as they come from another address than the broadcast went to; `info/2`
asks one device by its address, and its answer gets through.

  * `:to` - the addresses to ask, broadcast or not (default `{255, 255, 255, 255}`, and the
    broadcast address of each network this machine is on)
  * `:port` - the UDP port (default 48899)
  * `:timeout` - how long to wait for answers, in ms (default 2000)
  * `:codepage` - of the devices' names (default Windows-1252; see `ADS.Codepage`)

---

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