# `EtherCAT.MainDevice`
[🔗](https://github.com/tallakt/yaethercat/blob/v0.1.0/lib/ethercat/main_device.ex#L1)

The MainDevice of one EtherCAT segment: the process that owns its transport, sends frames, and
matches the frames that come back to what was sent.

    {:ok, main} = EtherCAT.MainDevice.start_link(interface: "eth1")
    {:ok, subdevices} = EtherCAT.MainDevice.scan(main)
    {:ok, image} = EtherCAT.MainDevice.map(main)
    :ok = EtherCAT.MainDevice.request_state(main, :operational)

    outputs = EtherCAT.ProcessImage.put(image, EtherCAT.ProcessImage.outputs(image), 0x1008, {0x7000, 0x01}, true)
    {:ok, inputs} = EtherCAT.MainDevice.exchange(main, outputs)

`start_link/1` takes one of:

  * `interface:`, an Ethernet interface, through `EtherCAT.Transport.Packet` on Linux or
    `EtherCAT.Transport.BPF` on macOS
  * `udp: {host, port}`, through `EtherCAT.Transport.UDP`
  * `transport: {module, options}`, through a transport of your own

and `name:` to register it, and `redundant:` for a ring: a second interface, as `"eth2"`. On a
ring, `grace:` (2 ms) is how long the other copy of a frame is waited for after the first; see A
ring.

## Frames

`transact/3` sends any datagrams, as `EtherCAT.Datagram` describes them, and gives what came back.
Each frame carries an index of its own, 0 to 127, in every datagram, and its answer is the frame
that comes back with that index and the same datagrams, in their commands, lengths and addresses.
Several frames can be on the way at once. A frame that doesn't come back within the timeout is
lost.

## The segment

`scan/2` counts the SubDevices, gives each a station address, `0x1000` and up by position, and
reads what they are from their registers and SII. It changes no AL state.

`map/2` takes the segment to Init and up to Pre-Operational, setting up the mailboxes, reads the
PDOs each SubDevice has assigned, from CoE where it has it and from its SII where it doesn't, and
maps their sync managers into one `EtherCAT.ProcessImage` with the FMMUs. It leaves the segment in
Pre-Operational.

`request_state/3` takes SubDevices to a state, through the ones between. On the way to
Operational it exchanges process data, since a SubDevice wants outputs before it goes there; from
then on the outputs must keep coming within the sync manager watchdog, or the SubDevices drop
them.

## Process data

`exchange/3` sends the outputs and gives back the inputs, in one LRW for the image (or as many as
it takes), with a read of every SubDevice's AL status in the same frame. It gives `{:ok, inputs}`
only when every working counter is what the image expects and the AL states are those
`request_state/3` left; otherwise `{:error, {:working_counter, expected, got}}`, or
`{:error, {:state, [{station, state, al_status_code}]}}` for the SubDevices not in the state they
were in, or `{:error, :timeout}`.

## A ring

With `redundant:`, a cable runs from the far end of the segment back to a second port, and every
frame goes out of both, its copy out of the secondary port with the index plus 128. SubDevices
process a frame only on its way out from the primary port: while the ring is whole, that port's
copy goes round through all of them to the secondary port, and is the answer. Where a cable is
broken, each copy comes back turned at the break, from the SubDevices on its side, and the answer
is the two put together. After the first copy of a frame, the other gets a grace, `grace:`; one
that doesn't come, as with a cable to a port out, leaves the frame done with the first. `ring/1`
says which it is, and `links/1` where.

The grace has to cover how far apart the two copies of a frame come back: a fraction of a
millisecond on a quiet machine, longer where the MainDevice may be late to read them, as in a
node still starting. A frame
whose second copy comes after the grace is done without it, and fails. It also has to stay well
under the timeout, as an exchange whose copy was lost waits the grace.

`scan/2` sets the loop control of the two ports at the ends of a ring, where its cables to the
MainDevice are, to Auto close, and of every other port to Auto. When a cable to the MainDevice
comes back, the port at its end then stays closed, turning frames back, until a frame from the
MainDevice's port comes in at it: none go into a NIC still bringing its link up. The far end is
found from the links of every SubDevice on a whole ring, by the scan, or for a ring scanned while
broken, the first time the MainDevice sees it whole.

The MainDevice keeps no time of its own: frames go when asked. It never logs, and a call never
exits the caller: when the MainDevice is gone or its transport is down, the result says so.

# `exchange_error`

```elixir
@type exchange_error() ::
  {:working_counter, non_neg_integer(), non_neg_integer()}
  | {:state, [{0..65535, EtherCAT.AL.state() | byte(), 0..65535}]}
  | :timeout
  | :closed
  | :not_mapped
  | term()
```

Why an exchange gave no inputs.

# `result`

```elixir
@type result() :: {:ok, binary(), 0..65535} | {:error, :timeout | :closed | term()}
```

The result of one datagram: what it read, or what was sent for one that only writes, and its
working counter; or why there is none.

# `t`

```elixir
@type t() :: GenServer.server()
```

A MainDevice, its pid or registered name.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `exchange`

```elixir
@spec exchange(t(), binary(), keyword()) ::
  {:ok, binary()} | {:error, exchange_error()}
```

Sends the outputs, a binary the size of the image's, and gives back the inputs. See the module
docs.

Options:

  * `timeout:` (20 ms), how long to wait for the frame. Keep it well within the SubDevices'
    sync manager watchdog: while an exchange waits for a frame that was lost, no outputs reach
    them, and when the watchdog runs out they drop to Safe-Operational.

Outputs of the wrong size raise in the caller.

# `image`

```elixir
@spec image(t()) :: EtherCAT.ProcessImage.t() | nil
```

The process image of the last map, or nil.

# `links`

```elixir
@spec links(t()) :: [
  {0..65535,
   %{required(0..3) =&gt; %{link: boolean(), loop: boolean()}} | :no_answer}
]
```

The ports of each SubDevice of the last scan, from its DL status: whether each has a link, and
whether its loop is closed, turning frames back. Where a ring is broken, the SubDevices on either
side of the break have a closed loop on the port towards it.

    [{0x1000, %{0 => %{link: true, loop: false}, 1 => %{link: true, loop: false}, ...}}, ...]

# `map`

```elixir
@spec map(t(), keyword()) :: {:ok, EtherCAT.ProcessImage.t()} | {:error, term()}
```

Maps the process data of the SubDevices into an image. See the module docs.

Options:

  * `stations:` (all), the SubDevices to map, by station address; the others are taken to
    Pre-Operational with them, and left out of the image
  * `timeout:` (5 seconds) for each change of state

# `request_state`

```elixir
@spec request_state(t(), EtherCAT.AL.state(), keyword()) ::
  :ok | {:error, [{0..65535, term()}]}
```

Takes SubDevices to a state, through the states between, and waits until they are there: `:ok`, or
`{:error, [{station, reason}]}` for those that aren't.

Options:

  * `stations:` (all), the SubDevices, by station address
  * `timeout:` (5 seconds), how long each change may take
  * `sync_manager_watchdog:`, in nanoseconds: set on the way to Safe-Operational, for how long the
    SubDevices keep their outputs without new ones; 0 turns it off; without it, they keep what
    they have, 100 ms by default
  * `outputs:`, the outputs sent on the way to Operational, all zeros by default

# `ring`

```elixir
@spec ring(t()) :: %{state: :intact | :broken | :unknown | :single, ports: map()}
```

The ring of a MainDevice with two ports: whether it's whole, as the last copy of a frame to come
back showed, and each port's state.

    %{state: :intact, ports: %{primary: :up, secondary: :up}}

The state is `:intact`, `:broken`, `:unknown` before a frame has shown it, or `:single` for a
MainDevice with one port.

# `scan`

```elixir
@spec scan(t(), keyword()) :: {:ok, [EtherCAT.SubDevice.t()]} | {:error, term()}
```

Finds the SubDevices, gives them their station addresses, and reads what they are. See the module
docs.

Options:

  * `timeout:` (100 ms), how long to wait for each frame

# `send_exchange`

```elixir
@spec send_exchange(t(), binary(), keyword()) :: reference()
```

As `exchange/3`, without waiting: the result comes to the caller as
`{EtherCAT.MainDevice, ref, result}`. Outputs of the wrong size give
`{:error, {:outputs_size, size}}` there.

# `send_transact`

```elixir
@spec send_transact(t(), [EtherCAT.Datagram.request()], keyword()) :: reference()
```

As `transact/3`, without waiting: the results come to the caller as
`{EtherCAT.MainDevice, ref, results}`.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Starts a MainDevice, linked to the caller. See the module docs for the options.

# `stop`

```elixir
@spec stop(t()) :: :ok
```

Stops a MainDevice, closing its transport.

# `subdevices`

```elixir
@spec subdevices(t()) :: [EtherCAT.SubDevice.t()]
```

The SubDevices of the last scan, in order.

# `transact`

```elixir
@spec transact(t(), [EtherCAT.Datagram.request()], keyword()) :: [result()]
```

Sends datagrams, as `EtherCAT.Datagram` describes them, and waits for them to come back: a result
for each, in their order. They go in one frame when they fit, and in as few as they fit otherwise,
all at once.

Options:

  * `timeout:` (100 ms), how long to wait for each frame

A request that isn't a datagram raises in the caller, and nothing is sent.

---

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