pysoem
pysoem copied to clipboard
Not reaching operational state
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)
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.
The Al_status_code is 0x2c (Fatal sync error) ...
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?
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.
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.
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.
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.
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.
Closed due to inactivity of the OP.