The EtherCAT master

Getting started

Ethercat terminals are usually connected in a loop with the EtherCAT master. The EtherCAT master has to know the order and function of these terminals. The list of terminals then has to be given in correct order to the constructor of the EtherCAT master object as follows:

from ebpfcat.ebpfcat import FastEtherCat
from ebpfcat.terminals import EL4104, Generic

out = EL4104()
unknown = Generic()  # use "Generic" for terminals of unknown type

master = FastEtherCat("eth0", [out, unknown])

Once we have defined the order of devices, we can connect to the loop and scan it to actually find all terminals. This takes time, so in a good asyncronous fashion we need to use await, which can only be done in an async function:

await master.connect()
await master.scan_bus()

The terminals usually control some devices, where one terminal may control several devices, or one device is controlled by several terminals. The devices are represented by Device objects. Upon instantiation, they are connected to the terminals:

from ebpfcat.devices import AnalogOutput

ao = AnalogOutput(out.ch1_value)

Devices are grouped into SyncGroup, which means that their terminals are always read and written at the same time. A device can only belong to one SyncGroup, but a terminal may be part of several devices or sync groups. The sync group is also responsible to constantly transfer data to and from the terminals such that they do not time out and go into a safe state:

from ebpfcat.ebpfcat import SyncGroup

sg = SyncGroup(master, [ao])  # this sync group only contains one terminal

sg.start()  # start operating the terminals

The AnalogOutput in the examples is a pretty boring device, it can only output a value like so:

ao.value = 5  # set the value on the terminal

Writing a device

Equipment controlled via the EtherCAT terminals often requires that a dedicated device is written for it. Devices inherit from ebpfcat.Device. They declare which kind of data they want to communicate to the terminals as a TerminalVar like so:

from ebpfcat.ebpfcat import Device

class Motor(Device):
    speed = TerminalVar()
    position = TerminalVar()

Before they can be used, their TerminalVars need to be initialized:

motor = Motor()
motor.speed = outputTerminal.speed
motor.position = encoderTerminal.value

whenever new data is read from the loop, the update method of the device is called, in which one can evaluate the TerminalVars:

def update(self):
    """a idiotic speed controller"""
    self.speed = (self.position - self.target) * self.pConst

Three methods of control

The communication with the terminals can happen in three different ways:

  • out-of-order: the communication happens ad-hoc whenever needed. This is done during initialization and for reading and writing configuration data, like CoE.

  • slow: the data is sent, received and processed via Python. This is good enough to around 100 Hz operation.

  • fast: the data is sent, received and processed using XDP in the Linux Kernel. Only very limited operations can be done, but the loop cycle frequency exceeds 10 kHz.

Reference Documentation

A collection of devices

This modules contains a collection of devices which may be helpful in many projects.

class ebpfcat.devices.AnalogInput(data)

Generic analog input device

This device can be linked to an analog input of a terminal. It will read from there and return the result in its parameter value.

class ebpfcat.devices.AnalogOutput(data)

Generic analog output device

This device can be linked to an analog output of a terminal. It will write the value to that terminal.

class ebpfcat.devices.Counter

A fake device counting the loops

class ebpfcat.devices.DigitalInput(data)

Generic digital input device

This device can be linked to an analog input of a terminal. It will read from there and return the result in its parameter value.

class ebpfcat.devices.DigitalOutput(data)

Generic digital output device

This device can be linked to an analog output of a terminal. It will write the value to that terminal.

class ebpfcat.devices.Dummy(terminals)

A placeholder device assuring a terminal is initialized

class ebpfcat.devices.Motor
class ebpfcat.devices.PWM(data)

Generic digital output device

This device can be linked to an analog output of a terminal. It will write the value to that terminal.

class ebpfcat.devices.RandomDropper

Low-level access to EtherCAT

this modules contains the code to actually talk to EtherCAT terminals.

class ebpfcat.ethercat.CoECmd(value)

An enumeration.

class ebpfcat.ethercat.ECCmd(value)

An enumeration.

class ebpfcat.ethercat.ECDataType(value)

An enumeration.

class ebpfcat.ethercat.EtherCat(network)

The EtherCAT connection

An object of this class represents one connection to an EtherCAT loop. It keeps the socket, and eventually all data flows through it.

This class supports both to send individual datagrams and wait for their response, but also to send and receive entire packets.

async connect()

connect to the EtherCAT loop

connection_made(transport)

start the send loop once the connection is made

async count()

Count the number of terminals on the bus

datagram_received(data, addr)

distribute received packets to the recipients

async find_free_address()

Find an absolute address currently not in use

async receive_index(index)

Wait for packet identified by index

async roundtrip(cmd, pos, offset, *args, data=None, idx=0)

Send a datagram and wait for its response

Parameters:
  • cmd (ECCmd) – the EtherCAT command

  • pos – the positional address of the terminal

  • offset – the offset within the terminal

  • idx – the EtherCAT datagram index

  • data – the data to be sent, or and integer for the number of zeros to be sent as placeholder

Any additional parameters will be interpreted as follows: every str is interpreted as a format for a struct.pack, everything else is the data for those format. Upon returning, the received data will be unpacked accoding to the format strings.

async roundtrip_packet(packet)

Send a packet and return the response

Send the packet to the loop and wait that it comes back, and return that to the caller.

send_packet(packet)

simply send the packet, fire-and-forget

async sendloop()

the eternal datagram sending loop

This method runs while we are connected, takes the datagrams to be sent from a queue, packs them in a packet and ships them out.

exception ebpfcat.ethercat.EtherCatError
class ebpfcat.ethercat.MBXType(value)

An enumeration.

class ebpfcat.ethercat.ODCmd(value)

An enumeration.

class ebpfcat.ethercat.Packet

An EtherCAT packet representation

A packet contains one or more datagrams which are sent as EtherNet packets. We implicitly add a datagram in the front which later serves as an identifier for the packet.

append(cmd, data, idx, *address)

Append a datagram to the packet

Parameters:
  • cmd (ECCmd) – EtherCAT command

  • data – the data in the datagram

  • idx – the datagram index, unchanged by terminals

Depending on the command, one or two more parameters represent the address, either terminal and offset for position or node addressing, or one value for logical addressing.

assemble(index)

Assemble the datagrams into a packet

Parameters:

index – an identifier for the packet

An implicit empty datagram is added at the beginning of the packet that may be used as an identifier for the packet.

full()

Is the data limit reached?

class ebpfcat.ethercat.Terminal

Represent one terminal (“slave”) in the loop

async eeprom_write_one(start, data)

read 2 bytes from the eeprom at start

async get_error()

read the error register

async get_state()

get the current state

async initialize(relative, absolute)

Initialize the terminal

this sets up the connection to the terminal we represent.

Parameters:
  • relative – the position of the terminal in the loop, a negative number counted down from 0 for the first terminal

  • absolute – the number used to identify the terminal henceforth

This also reads the EEPROM and sets up the sync manager as defined therein. It still leaves the terminal in the init state.

async mbx_recv()

receive data from the mailbox

async mbx_send(type, *args, data=None, address=0, priority=0, channel=0)

send data to the mailbox

async read(start, *args, **kwargs)

read data from the terminal at offset start

see EtherCat.roundtrip for details on more parameters.

async read_eeprom()

read the entire eeprom

async set_state(state)

try to set the state, and return the new state

async to_operational(target=8)

try to bring the terminal to operational state

this tries to push the terminal through its state machine to the operational state. Note that even if it reaches there, the terminal will quickly return to pre-operational if no packets are sent to keep it operational.

async write(start, *args, **kwargs)

write data to the terminal at offset start

see EtherCat.roundtrip for details on more parameters