pysoem icon indicating copy to clipboard operation
pysoem copied to clipboard

Not reaching operational state

Open ziadchelala opened this issue 5 years ago • 8 comments

Hello @bnjmnp, I have a Beckhoff EK1100 and an EL7201 to which a Beckhoff servo motor is connected. I am trying to take the EL7201 into operational state. I did some modification to the basic_example.py and successfully moved to SAFEOP. However, I cannot go into OP state. Can you please have a look at the modification I made to try pinpointing the problem?

This is the message in the terminal: basic_example started incorrect wkc basic_example failed: not all slaves reached OP state

I tried printing out the wkc since I thought it was the problem but it turns out that after the thread runs, the actual wkc becomes equal to the expected wkc.

And below is the modified version of the basic_example (changing the expected layout as well):

import sys import struct import time import threading

from collections import namedtuple

import pysoem

class BasicExample:

BECKHOFF_VENDOR_ID = 0x0002
EK1100_PRODUCT_CODE = 0x044c2c52
EL7201_PRODUCT_CODE = 0x1C213052

def __init__(self, ifname):
    self._ifname = ifname
    self._pd_thread_stop_event = threading.Event()
    self._ch_thread_stop_event = threading.Event()
    self._actual_wkc = 0
    self._master = pysoem.Master()
    self._master.in_op = False
    self._master.do_check_state = False
    SlaveSet = namedtuple('SlaveSet', 'name product_code config_func')
    self._expected_slave_layout = {0: SlaveSet('EK1100', self.EK1100_PRODUCT_CODE, None),
                                   1: SlaveSet('EL7201', self.EL7201_PRODUCT_CODE, self.el7201_setup)}

def el7201_setup(self, slave_pos):
    slave = self._master.slaves[slave_pos]

    map_1c12_bytes = struct.pack('BxHH', 2, 0x1600, 0x1601)
    slave.sdo_write(0x1c12, 0, map_1c12_bytes, True)

    slave.dc_sync(1, 10000000,0,10000000)

def _processdata_thread(self):
    while not self._pd_thread_stop_event.is_set():
        self._master.send_processdata()
        self._actual_wkc = self._master.receive_processdata(10000)

        if not self._actual_wkc == self._master.expected_wkc:
            print('incorrect wkc')
        time.sleep(0.01)

def _pdo_update_loop(self):

    self._master.in_op = True
    output_len = len(self._master.slaves[2].output)

    tmp = bytearray([0 for i in range(output_len)])

    toggle = True
    try:
        while 1:
            if toggle:
                tmp[0] = 0x00
            else:
                tmp[0] = 0x02
            self._master.slaves[2].output = bytes(tmp)

            toggle ^= True

            time.sleep(1)

    except KeyboardInterrupt:
        # ctrl-C abort handling
        print('stopped')


def run(self):

    self._master.open(self._ifname)

    if not self._master.config_init() > 0:
        self._master.close()
        raise BasicExampleError('no slave found')

    for i, slave in enumerate(self._master.slaves):
        if not ((slave.man == self.BECKHOFF_VENDOR_ID) and
                (slave.id == self._expected_slave_layout[i].product_code)):
            self._master.close()
            raise BasicExampleError('unexpected slave layout')
        slave.config_func = self._expected_slave_layout[i].config_func
        slave.is_lost = False

    self._master.config_map()

    if self._master.state_check(pysoem.SAFEOP_STATE, 500000) != pysoem.SAFEOP_STATE:
        self._master.close()
        raise BasicExampleError('not all slaves reached SAFEOP state')

    self._master.state = pysoem.OP_STATE

    check_thread = threading.Thread(target=self._check_thread)
    check_thread.start()
    proc_thread = threading.Thread(target=self._processdata_thread)
    proc_thread.start()
    #print("counter after start thread=====>  ", self._actual_wkc)
    self._master.write_state()

    all_slaves_reached_op_state = False
    for i in range(40):
        self._master.state_check(pysoem.OP_STATE, 50000)
        if self._master.state == pysoem.OP_STATE:
            all_slaves_reached_op_state = True
            break

    if all_slaves_reached_op_state:
        self._pdo_update_loop()


    self._pd_thread_stop_event.set()
    self._ch_thread_stop_event.set()
    proc_thread.join()
    check_thread.join()
    self._master.state = pysoem.INIT_STATE
    # request INIT state for all slaves
    self._master.write_state()
    self._master.close()

    if not all_slaves_reached_op_state:
        raise BasicExampleError('not all slaves reached OP state')

@staticmethod
def _check_slave(slave, pos):
    if slave.state == (pysoem.SAFEOP_STATE + pysoem.STATE_ERROR):
        print(
            'ERROR : slave {} is in SAFE_OP + ERROR, attempting ack.'.format(pos))
        slave.state = pysoem.SAFEOP_STATE + pysoem.STATE_ACK
        slave.write_state()
    elif slave.state == pysoem.SAFEOP_STATE:
        print(
            'WARNING : slave {} is in SAFE_OP, try change to OPERATIONAL.'.format(pos))
        slave.state = pysoem.OP_STATE
        slave.write_state()
    elif slave.state > pysoem.NONE_STATE:
        if slave.reconfig():
            slave.is_lost = False
            print('MESSAGE : slave {} reconfigured'.format(pos))
    elif not slave.is_lost:
        slave.state_check(pysoem.OP_STATE)
        if slave.state == pysoem.NONE_STATE:
            slave.is_lost = True
            print('ERROR : slave {} lost'.format(pos))
    if slave.is_lost:
        if slave.state == pysoem.NONE_STATE:
            if slave.recover():
                slave.is_lost = False
                print(
                    'MESSAGE : slave {} recovered'.format(pos))
        else:
            slave.is_lost = False
            print('MESSAGE : slave {} found'.format(pos))

def _check_thread(self):

    while not self._ch_thread_stop_event.is_set():
        if self._master.in_op and ((self._actual_wkc < self._master.expected_wkc) or self._master.do_check_state):
            self._master.do_check_state = False
            self._master.read_state()
            for i, slave in enumerate(self._master.slaves):
                if slave.state != pysoem.OP_STATE:
                    self._master.do_check_state = True
                    BasicExample._check_slave(slave, i)
            if not self._master.do_check_state:
                print('OK : all slaves resumed OPERATIONAL.')

        time.sleep(0.01)

class BasicExampleError(Exception): def init(self, message): super(BasicExampleError, self).init(message) self.message = message

if name == 'main':

print('basic_example started')

if len(sys.argv) > 1:
    try:
        BasicExample(sys.argv[1]).run()
    except BasicExampleError as expt:
        print('basic_example failed: ' + expt.message)
        sys.exit(1)
else:
    print('usage: basic_example ifname')
    sys.exit(1)

ziadchelala avatar Jul 29 '19 14:07 ziadchelala

Hi @ziadchelala, my guesses are that the EL7201 needs more configuration in the el7201_setup function to get into OP state. Or maybe you need to feed more plausible data to the EL7201 in the _pdo_update_loop. You could check "al status code" to have a clue why the EL7201 is not reaching OP state, by changing this part:

        for i in range(40):
            self._master.state_check(pysoem.OP_STATE, 50000)
            if self._master.state == pysoem.OP_STATE:
                all_slaves_reached_op_state = True
                break

to something like this:

        for i in range(40):
            self._master.state_check(pysoem.OP_STATE, 50000)
            if self._master.state == pysoem.OP_STATE:
                all_slaves_reached_op_state = True
                break
            else:
                self._master.read_state()
                for slave in self._master.slaves:
                    if not slave.state == pysoem.OP_STATE:
                        print('{} did not reach OP state'.format(slave.name()))
                        print('al status code {} ({})'.format(hex(slave.al_status),
                                                              pysoem.al_status_code_to_string(slave.al_status)))

Like in the minimal_example.py.

Site note: If you place triple backticks ``` before and after code blocks, within your GitHub comments, you get nicer code blocks. You can even have syntax highlighting: https://help.github.com/en/articles/creating-and-highlighting-code-blocks.

bnjmnp avatar Jul 30 '19 08:07 bnjmnp

The Al_status_code is 0x2c (Fatal sync error) ...

ziadchelala avatar Jul 31 '19 12:07 ziadchelala

Hello @bnjmnp, I am still trying to make the pysoem work, however, when comparing my pysoem script to the working code in SOEM, I found out that the problem might be the synchronization of Linux with EtherCAT. In SOEM, they are using a value called ec_DCtime to do the synchronization. I tried getting this value in pysoem, but I am not able to find it. Could you please tell me how to get this value in pysoem?

ziadchelala avatar Aug 05 '19 16:08 ziadchelala

The ec_DCtime value is currently not exposed by the wrapper. Just digging shortly into this, I guess reading this value is required to synchronize the EtherCAT cycle (send_processdata and receive_processdata) with sync0 cycle. So I will make ec_DCtime accessible as soon as possible. Did you try calling self._master.config_dc() directly after the call of self._master.config_map()? I think this is also required to get correct synchronisation.

bnjmnp avatar Aug 05 '19 21:08 bnjmnp

I tried adding the self._master.config_dc() but it did not fix the issue. Having the ec_DCtime is crucial to get the pysoem running. Therefore, if it is possible, guide us on how to add it to the code so we can get the motor running.

ziadchelala avatar Aug 06 '19 09:08 ziadchelala

See my referenced commit above. Taken your example code, with self._master.dc_time you should now have read access to ec_DCtime. Without calling self._master.config_dc() the dc_time remains zero.

I hope this helps.

bnjmnp avatar Aug 06 '19 19:08 bnjmnp

Hello again, I updated the repo and encountered the following error:

Exception in thread Thread-1: Traceback (most recent call last): File "/usr/local/lib/python3.7/threading.py", line 917, in _bootstrap_inner self.run() File "/usr/local/lib/python3.7/threading.py", line 865, in run self._target(*self._args, **self._kwargs) File "ziad_test.py", line 136, in _processdata_thread print("ec_DCtime: ",self._master.dc_time) AttributeError: 'Master' object has no attribute 'dc_time'

It seems as if changes in the pysoem.pyx file are not effective even though I have been using the sudo python3.7 setup.py build_ext --inplace command. PS: when I install the pysoem using python3.7 -m pip install pysoem, I still get v.0.016.

ziadchelala avatar Aug 07 '19 16:08 ziadchelala

Are you sure that when you are calling ziad_test.py, your Python interpreter is really using the inplace build pysoem? Your inplace setup.py call looks fine, and changes in the pysoem.pyx file should always lead to a recompile.

I have just updated the PyPI package so you should now be able to install the update via pip.

bnjmnp avatar Aug 07 '19 19:08 bnjmnp

Closed due to inactivity of the OP.

bnjmnp avatar Jan 03 '23 19:01 bnjmnp