2.3.3. Generate Python Code
To enable simple integration of EtherCAT devices in Python, the voraus-ecat package provides a command line tool
for automatic code generation. The command generates the fieldbus.py file for the EtherCAT master with the URL
opc.tcp://localhost:4840 inside the dio_example directory.
voraus-ecat generate opc.tcp://localhost:4840 fieldbus.py
📂dio_example/
🖹fieldbus.py
🖹docker-compose.yml
📂data/
🖹eni.xml
2.3.3.1. Master in Dummy Mode
The Python client cannot read the ENI file by itself. A running EtherCAT master instance is required to retrieve the data for code generation. In a development environment (without the real fieldbus setup), the voraus EtherCAT Master can be started in a ‘dummy’ mode on the local machine. A dummy instance can be launched, e.g., with the provided compose file in the example:
docker compose -f docker-compose-dummy.yml up
docker-compose-dummy.yml
1services:
2 ethercat-master-dummy:
3 image: voraus.jfrog.io/docker/voraus-ethercat-master:1.3.0
4 network_mode: host # for direct link to codemeter runtime on host (alternative: codemeter container)
5 pid: host # for direct link to codemeter runtime on host (alternative: codemeter container)
6 volumes:
7 - ./data/:/root/data/ # contains eni file and logs
8 environment:
9 - ECAT_ENI=/root/data/eni.xml
10 - ECAT_LOG_DIR=/root/data/log
11 - ECAT_OPCUA_PORT=${OPCUA_PORT} # change port here or define environment variable
12 command: [ "dummy_opcua_server" ]
2.3.3.2. Auto-Generated Code
The auto-generated code contains two classes: the inputs and outputs of the process data image. The respective process
data objects (PDOs) are defined as members of the classes. For example, the digital inputs of the EL1008
EtherCAT terminal are contained in the Inputs class, while the digital outputs of the EL2008 terminal
are contained in the Outputs class, see the following code example.
fieldbus.py (auto-generated)
1"""Autogenerated code for cyclic communication mode."""
2
3from voraus_ecat import EtherCAT, ProcessData, pdo
4
5
6class Inputs(ProcessData):
7 """Defines process inputs."""
8
9 def __init__(self) -> None:
10 """Initializes process inputs."""
11 super().__init__()
12
13 self.term_2_el1008_channel_1_input = pdo.Bit1("1:Term 2 (EL1008).Channel 1.Input")
14 self.term_2_el1008_channel_2_input = pdo.Bit1("1:Term 2 (EL1008).Channel 2.Input")
15 self.term_2_el1008_channel_3_input = pdo.Bit1("1:Term 2 (EL1008).Channel 3.Input")
16 self.term_2_el1008_channel_4_input = pdo.Bit1("1:Term 2 (EL1008).Channel 4.Input")
17 self.term_2_el1008_channel_5_input = pdo.Bit1("1:Term 2 (EL1008).Channel 5.Input")
18 self.term_2_el1008_channel_6_input = pdo.Bit1("1:Term 2 (EL1008).Channel 6.Input")
19 self.term_2_el1008_channel_7_input = pdo.Bit1("1:Term 2 (EL1008).Channel 7.Input")
20 self.term_2_el1008_channel_8_input = pdo.Bit1("1:Term 2 (EL1008).Channel 8.Input")
21
22
23class Outputs(ProcessData):
24 """Defines process outputs."""
25
26 def __init__(self) -> None:
27 """Initializes process outputs."""
28 super().__init__()
29
30 self.term_3_el2008_channel_1_output = pdo.Bit1("1:Term 3 (EL2008).Channel 1.Output")
31 self.term_3_el2008_channel_2_output = pdo.Bit1("1:Term 3 (EL2008).Channel 2.Output")
32 self.term_3_el2008_channel_3_output = pdo.Bit1("1:Term 3 (EL2008).Channel 3.Output")
33 self.term_3_el2008_channel_4_output = pdo.Bit1("1:Term 3 (EL2008).Channel 4.Output")
34 self.term_3_el2008_channel_5_output = pdo.Bit1("1:Term 3 (EL2008).Channel 5.Output")
35 self.term_3_el2008_channel_6_output = pdo.Bit1("1:Term 3 (EL2008).Channel 6.Output")
36 self.term_3_el2008_channel_7_output = pdo.Bit1("1:Term 3 (EL2008).Channel 7.Output")
37 self.term_3_el2008_channel_8_output = pdo.Bit1("1:Term 3 (EL2008).Channel 8.Output")
2.3.3.3. Custom Code
The auto-generated input and output classes can now be used in a separate Python script, module, or package. The
program.py file is created for this purpose.
📂dio_example/
🖹program.py
🖹fieldbus.py
🖹docker-compose.yml
📂data/
🖹eni.xml
In the file program.py, the input and output classes are first imported and then referenced to the
EtherCAT object during initialization. Once the connection has been established and the transition to the
operational state has been made, PDOs can be read and written.
program.py (extended)
1# wupi: Encryption=False
2# pylint: disable=duplicate-code
3"""An example using PDOs from auto-generated dio_example_pdos module."""
4
5import time
6from os import environ
7
8from fieldbus import Inputs, Outputs
9
10from voraus_ecat import EtherCAT
11from voraus_ecat.exceptions import EtherCATError
12
13if __name__ == "__main__":
14 ethercat = EtherCAT(inputs=Inputs(), outputs=Outputs())
15 url = environ.get("VECAT_URL", "opc.tcp://localhost:4841")
16
17 try:
18 with ethercat.connection(url):
19 # Set the master to operational state.
20 ethercat.set_op_state()
21 # Read PDOs initially.
22 ethercat.read_pdos()
23
24 # Write output.
25 ethercat.outputs.term_3_el2008_channel_2_output.set(True)
26 ethercat.write_pdos()
27
28 # Wait for input with timeout.
29 start = time.time()
30 while not ethercat.inputs.term_2_el1008_channel_5_input.get():
31 ethercat.read_pdos()
32
33 if time.time() - start > 5.0:
34 raise TimeoutError("Input is not high.")
35 except EtherCATError as e:
36 print(e)
2.3.3.4. Integrate with Version Control System
The separation between process data definition and usage enables the re-generation of Python code after a change of the system, such as the addition of an EtherCAT terminal. This change can then be processed as a pull request with version management software, e.g., Git.