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.

voraus-ecat generate opc.tcp://localhost:4840 dio_example_pdos.py

The command above generates the dio_example_pdos.py file for the EtherCAT master with the URL opc.tcp://localhost:4840 inside the dio_example directory.

📂dio_example/
  🖹dio_example_pdos.py
  🖹docker-compose.yml
  📂data/
    🖹eni.xml

3.1. 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.

dio_example_pdos.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")

3.2. Custom Code

The auto-generated input and output classes can now be used in a separate Python script, module or package. The dio_example.py file is created for this purpose.

📂dio_example/
  🖹dio_example.py
  🖹dio_example_pdos.py
  🖹docker-compose.yml
  📂data/
    🖹eni.xml

In the file dio_example.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.

dio_example.py (extended)

 1"""An example using PDOs from auto-generated dio_example_pdos module."""
 2
 3import time
 4
 5from voraus_ecat import EtherCAT
 6
 7from .dio_example_pdos import Inputs, Outputs
 8
 9
10def main(uri: str) -> None:
11    ethercat = EtherCAT(inputs=Inputs(), outputs=Outputs())
12
13    with ethercat.connection(uri):
14        # Set the master to operational state.
15        ethercat.set_op_state()
16        # Read PDOs initially.
17        ethercat.read_pdos()
18
19        # Write output.
20        ethercat.outputs.term_3_el2008_channel_2_output.set(True)
21        ethercat.write_pdos()
22
23        # Wait for input with timeout.
24        start = time.time()
25        while not ethercat.inputs.term_2_el1008_channel_5_input.get():
26            ethercat.read_pdos()
27
28            if time.time() - start > 5.0:
29                raise TimeoutError("Input is not high.")
30
31
32if __name__ == "__main__":
33    main("opc.tcp://localhost:4840")

3.3. Integrate with Git

The separation between process data definition and usage enables the re-generation of python code after a change to the system, such as the addition of an EtherCAT terminal. This change can then be processed as a pull request with version management software such as Git.