3.3.4. Error Handling

Two types of errors can be distinguished:

  • Client-side Errors: Errors that occur in the Python client - e.g., logic errors in the Python code or failed network connection.

  • Server-side Errors: Errors that occur in the voraus EtherCAT Master application - e.g., misconfiguration or connection problems on the EtherCAT bus.

The status of the EtherCAT master application (server) can be obtained by ethercat.get_system_info(). For more details related to the server-side error handling, refer to the voraus EtherCAT Master documentation. Client-side errors are typically handled as Python exceptions. Log messages from the master application are also available through the Python logger: logging.getLogger("voraus_ecat.master").

The available error handling options are best demonstrated with an example.

3.3.4.1. Example Code

Error Handling Example

  1# pylint: disable=duplicate-code
  2"""A very simple example for error handling."""
  3
  4import logging
  5import time
  6from os import environ
  7
  8from fieldbus import Inputs, Outputs
  9
 10from voraus_ecat import EtherCAT, State, SystemInfo, SystemState
 11from voraus_ecat.exceptions import EtherCATError, PDOError
 12
 13# create a simple application logger
 14_logger = logging.getLogger("application")
 15logging.getLogger().addHandler(logging.StreamHandler())
 16
 17ERROR_TRANSITION_TIMEOUT = 2.0
 18STATE_TIMEOUT = 10.0
 19NO_FAULTS_TIMEOUT_SECONDS = 2.0
 20
 21
 22def wait_for_no_faults(ecat: EtherCAT, timeout: float) -> bool:
 23    """Waits until the master detects no communication faults anymore and returns whether it does."""
 24    deadline = time.monotonic() + timeout
 25    while ecat.get_cyclic_fault_indicator() > 0:
 26        if time.monotonic() > deadline:
 27            return False
 28        time.sleep(0.1)
 29    return True
 30
 31
 32def try_to_resolve_error(ecat: EtherCAT, info: SystemInfo) -> None:
 33    """A simple error reaction that resets the error and brings the master back to OP."""
 34    _logger.info(f"System State: {info.state.name}")
 35    _logger.info(f"Error: {info.error_message} (#{info.error_number})")
 36
 37    if info.state == SystemState.ERR_TRANS:
 38        # The master keeps the EtherCAT communication active for a configured time period. This is the time to bring
 39        # the devices into a safe state.
 40        _logger.info("Waiting for ERROR state...")
 41        ecat.wait_for_state(SystemState.ERROR, timeout=ERROR_TRANSITION_TIMEOUT)
 42
 43    # In the Error state the bus is brought to a safe state.
 44    # The master only accepts reset when it detects no communication faults anymore.
 45    # Depending on the Error, it might be necessary to first "resolve" the error.
 46    # E.g. if Error was entered because the power supply of a device was interrupted, the device is probably in INIT
 47    # now, while all other devices are in SAFEOP.
 48    # Most of these situations can be resolved by requesting INIT first.
 49    _logger.info("Waiting for the communication faults to decay...")
 50    if not wait_for_no_faults(ecat, NO_FAULTS_TIMEOUT_SECONDS):
 51        _logger.warning(f"Communication faults persist (indicator: {ecat.get_cyclic_fault_indicator()})")
 52        _logger.info("Requesting INIT state...")
 53        ecat.set_state(State.INIT, timeout=STATE_TIMEOUT)
 54        if not wait_for_no_faults(ecat, NO_FAULTS_TIMEOUT_SECONDS):
 55            raise TimeoutError(f"Communication faults persist in INIT after {NO_FAULTS_TIMEOUT_SECONDS} seconds.")
 56
 57    _logger.info("Resetting the error...")
 58    ecat.reset()
 59
 60    _logger.info("Waiting for CONFIG state...")
 61    ecat.wait_for_state(SystemState.CONFIG, timeout=STATE_TIMEOUT)
 62    # The master does not request an EtherCAT state on its own after the reset.
 63    _logger.info("Trying to get back to OP...")
 64    ecat.set_op_state(timeout=STATE_TIMEOUT)
 65    ecat.wait_for_state(SystemState.ACTIVE, timeout=STATE_TIMEOUT)
 66    _logger.info("Ready to go.")
 67
 68
 69if __name__ == "__main__":
 70    ethercat = EtherCAT(inputs=Inputs(), outputs=Outputs())
 71    url = environ.get("VECAT_URL", "opc.tcp://localhost:4841")
 72
 73    try:
 74        with ethercat.connection(url):
 75            # Set the master to operational state.
 76            ethercat.set_op_state()
 77
 78            while True:
 79
 80                ethercat.read_pdos()
 81                # [do something...]
 82                ethercat.write_pdos()
 83
 84                time.sleep(0.1)
 85
 86                # check for errors
 87                system_info = ethercat.get_system_info()
 88                if system_info.state in (SystemState.ERR_TRANS, SystemState.ERROR):
 89                    try_to_resolve_error(ethercat, system_info)
 90
 91    except EtherCATError as e:
 92        # the master rejected a request, e.g. the reset or a state request
 93        _logger.error(e)
 94    except ConnectionError as e:
 95        # connection to EtherCAT master was lost (or never established)
 96        _logger.error(f"No connection to EtherCAT master ({url})")
 97    except PDOError as e:
 98        # This happens if PDO values can not be read (e.g. if master is not in OP state)
 99        _logger.error(e)
100    except TimeoutError as e:
101        # a state was not reached in time or communication faults persist, e.g. a device does not come back
102        _logger.error(e)