EtherCAT read task
Learn how to acquire data from EtherCAT devices with Synnax.
This task follows the standard task lifecycle.
Before creating a read task, configure your EtherCAT devices and identify the TxPDOs (input PDOs) to read from each one.
All channels in a task must use devices connected to the same network interface. Create separate tasks for devices on different EtherCAT networks.
Task configuration reference
Channel configuration
Each channel reads one TxPDO entry at its CoE index and sub-index. The Driver gets the size of the entry from the device, so the data type of the channel must be the same size.
Entry channel
Reads one TxPDO entry.
The Console lists each entry the device reports, with its address. In code, find the
address of an entry by name with ethercat.find_entry in Python or ethercat.findEntry
in TypeScript.
A task from an earlier release can hold an "unresolved" channel: one that named an
entry the device does not have, or has more than once. Pick an entry for it in the
Console before the task runs.
CoE (CAN over EtherCAT) addressing uses a 16-bit index and 8-bit subindex. Common
servo drive objects include status word (0x6041), position actual (0x6064), and
velocity actual (0x606C).
Important rules
- Network constraint -> All channels must use devices on the same network interface.
- Hardware timing -> EtherCAT timing is deterministic. The sample rate sets how often the task takes a sample from the bus. See how the Driver runs a network.
- Stream rate -> For sample rates under 50 Hz, set the stream rate equal to the sample rate. Above that, keep the stream rate under 50 Hz.
- Timestamps -> Each sample has the time of the bus cycle that read it. If a device on the network has a distributed clock (DC), the SOEM master measures that time with the DC, so the timestamps show when the frame passed the devices, without the jitter of the host clock. They stay aligned with the host clock. When the host computer is busy, a cycle can still run late, and its timestamp shows the delay.
How-to
Console
Python
TypeScript
Configure and run task
import synnax as sy
from synnax import ethercat
client = sy.Synnax()
# Retrieve EtherCAT device
dev = client.devices.retrieve(name="EL3102")
# Create index channel for timestamps
ec_time = client.channels.create(
name="ec_read_time",
is_index=True,
data_type=sy.DataType.TIMESTAMP,
retrieve_if_name_exists=True,
)
# Create data channels matching PDO data types
# Check device properties for available PDOs and their types
status = client.channels.create(
name="status_word",
index=ec_time.key,
data_type=sy.DataType.UINT16,
retrieve_if_name_exists=True,
)
position = client.channels.create(
name="position_actual",
index=ec_time.key,
data_type=sy.DataType.INT32,
retrieve_if_name_exists=True,
)
# Find the address of each entry by name
status_entry = ethercat.find_entry(dev, "Statusword")
position_entry = ethercat.find_entry(dev, "Position actual value")
task = ethercat.ReadTask(
name="EtherCAT Read Task",
sample_rate=sy.Rate.HZ * 1000,
stream_rate=sy.Rate.HZ * 50,
channels=[
ethercat.EntryReadChannel(
device=dev.key,
index=status_entry.index,
sub_index=status_entry.sub_index,
data_type=sy.DataType(status_entry.data_type),
channel=status.key,
),
ethercat.EntryReadChannel(
device=dev.key,
index=position_entry.index,
sub_index=position_entry.sub_index,
data_type=sy.DataType(position_entry.data_type),
channel=position.key,
),
],
)
client.tasks.configure(task)
# Start task and read data
with task.run():
with client.open_streamer(["status_word", "position_actual"]) as streamer:
for _ in range(10):
frame = streamer.read()
print(f"Status: {frame['status_word'][-1]}, "
f"Position: {frame['position_actual'][-1]}") Edit task configuration
import synnax as sy
from synnax import ethercat
client = sy.Synnax()
# Retrieve the existing task
task = ethercat.ReadTask(internal=client.tasks.retrieve(name="EtherCAT Read Task"))
# Update task-level settings
task.config.sample_rate = sy.Rate.HZ * 2000
task.config.stream_rate = sy.Rate.HZ * 100
# Add a new channel
new_channel = client.channels.create(
name="velocity_actual",
index=client.channels.retrieve(name="ec_read_time").key,
data_type=sy.DataType.INT32,
retrieve_if_name_exists=True,
)
dev = client.devices.retrieve(key=task.config.channels[0].device)
velocity_entry = ethercat.find_entry(dev, "Velocity actual value")
task.config.channels.append(
ethercat.EntryReadChannel(
device=dev.key,
index=velocity_entry.index,
sub_index=velocity_entry.sub_index,
data_type=sy.DataType(velocity_entry.data_type),
channel=new_channel.key,
)
)
# Apply changes
client.tasks.configure(task) Configure and run task
import { Synnax } from "@synnaxlabs/client";
const client = new Synnax();
// Retrieve EtherCAT device
const [dev] = await client.devices.retrieve({ names: ["EL3102"] });
// Create index channel for timestamps
const ecTime = await client.channels.create(
{
name: "ec_read_time",
isIndex: true,
dataType: "timestamp",
},
{ retrieveIfNameExists: true },
);
// Create data channels matching PDO data types
const status = await client.channels.create(
{
name: "status_word",
index: ecTime.key,
dataType: "uint16",
},
{ retrieveIfNameExists: true },
);
const position = await client.channels.create(
{
name: "position_actual",
index: ecTime.key,
dataType: "int32",
},
{ retrieveIfNameExists: true },
);
// Create and configure the task with the CoE address of each entry
const task = await client.tasks.create({
name: "EtherCAT Read Task",
rack: dev.rack,
type: "ethercat_read",
config: {
sampleRate: 1000, // 1 kHz
streamRate: 50, // Stream at 50 Hz
channels: [
{
type: "entry",
device: dev.key,
index: 0x6041, // Statusword
subIndex: 0,
dataType: "uint16",
channel: status.key,
},
{
type: "entry",
device: dev.key,
index: 0x6064, // Position actual value
subIndex: 0,
dataType: "int32",
channel: position.key,
},
],
},
});
// Start task
await task.start();
// Read data
const streamer = await client.openStreamer(["status_word", "position_actual"]);
for (let i = 0; i < 10; i++) {
const frame = await streamer.read();
const statusVal = frame.get("status_word").at(-1);
const positionVal = frame.get("position_actual").at(-1);
console.log(`Status: ${statusVal}, Position: ${positionVal}`);
}
// Stop task
await task.stop();
await streamer.close(); Edit task configuration
import { ethercat, Synnax } from "@synnaxlabs/client";
const client = new Synnax();
// Retrieve existing task
const task = await client.tasks.retrieve({ name: "EtherCAT Read Task" });
// Parse current configuration
const config = ethercat.readConfigZ.parse(task.config);
// Update task-level settings
config.sampleRate = 2000; // Increase to 2 kHz
config.streamRate = 100; // Increase stream rate
// Add a new channel
const [ecTime] = await client.channels.retrieve({ names: ["ec_read_time"] });
const newChannel = await client.channels.create(
{
name: "velocity_actual",
index: ecTime.key,
dataType: "int32",
},
{ retrieveIfNameExists: true },
);
config.channels.push(
ethercat.entryReadChannelZ.parse({
type: "entry",
device: config.channels[0].device,
index: 0x606c, // Velocity actual value
subIndex: 0,
dataType: "int32",
channel: newChannel.key,
}),
);
// Apply changes
await client.tasks.create({
key: task.key,
rack: task.rack,
name: task.name,
type: task.type,
config,
});