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, throughEtherCAT.Transport.Packeton Linux orEtherCAT.Transport.BPFon macOSudp: {host, port}, throughEtherCAT.Transport.UDPtransport: {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.
Summary
Types
Why an exchange gave no inputs.
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.
A MainDevice, its pid or registered name.
Functions
Returns a specification to start this module under a supervisor.
Sends the outputs, a binary the size of the image's, and gives back the inputs. See the module docs.
The process image of the last map, or nil.
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.
Maps the process data of the SubDevices into an image. See the module docs.
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.
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.
Finds the SubDevices, gives them their station addresses, and reads what they are. See the module docs.
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.
As transact/3, without waiting: the results come to the caller as
{EtherCAT.MainDevice, ref, results}.
Starts a MainDevice, linked to the caller. See the module docs for the options.
Stops a MainDevice, closing its transport.
The SubDevices of the last scan, in order.
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.
Types
@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.
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.
@type t() :: GenServer.server()
A MainDevice, its pid or registered name.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@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.
@spec image(t()) :: EtherCAT.ProcessImage.t() | nil
The process image of the last map, or nil.
@spec links(t()) :: [ {0..65535, %{required(0..3) => %{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}, ...}}, ...]
@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 imagetimeout:(5 seconds) for each change of state
@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 addresstimeout:(5 seconds), how long each change may takesync_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 defaultoutputs:, the outputs sent on the way to Operational, all zeros by default
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.
@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
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.
@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}.
@spec start_link(keyword()) :: GenServer.on_start()
Starts a MainDevice, linked to the caller. See the module docs for the options.
@spec stop(t()) :: :ok
Stops a MainDevice, closing its transport.
@spec subdevices(t()) :: [EtherCAT.SubDevice.t()]
The SubDevices of the last scan, in order.
@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.