bnjmnp / pysoem

Cython wrapper for the Simple Open EtherCAT Master Library
MIT License
95 stars 36 forks source link

Failed to go to OP state and incorrect wkc #107

Closed Xubunt closed 11 months ago

Xubunt commented 1 year ago

Hello, I am trying to move my motor using modified code from issue #30. I am using the code below:

Xubunt commented 1 year ago

Manual: https://en.estun.com/static/upload/file/20220719/1658199238719685.pdf

@bnjmnp would appreciate any guidance

Xubunt commented 1 year ago

I ran minimal example.py and got the following status code:

1 slaves found and configured
Drive did not reach SAFEOP state
al status code 0x1d (Invalid output configuration)
not all slaves reached SAFEOP state
Xubunt commented 1 year ago

This was my modified code for minimal example.py

bnjmnp commented 1 year ago

Manual: https://en.estun.com/static/upload/file/20220719/1658199238719685.pdf

@bnjmnp would appreciate any guidance

This site dose not use https, which is strange.

Does the vendor also provide a ESI XML file for this device? There one might find what needs to go into the ed3l_setup() function to get rid of the "Invalid output configuration" error. Where you able to operate the device with any other EtherCAT master system?

Xubunt commented 1 year ago

1658199238719685.pdf

Here is the manual.

Xubunt commented 1 year ago

ESTUN_ED3L_V1_00B2-软件.txt Here is the .xml file I got from the vendor. I am not sure I know enough about Ethercat to identify what would need to be added for the correct configuration.

bnjmnp commented 1 year ago

To get a motor running in PV mode you could try adapting your first code with this:

def ed3l_config_func(slave_pos):
    global ed3l

    map_1c12_bytes = struct.pack('BxH', 1, 0x1600)
    ed3l.sdo_write(0x1c12, 0, map_1c12_bytes, True)

    map_1c13_bytes = struct.pack('BxH', 1, 0x1A00)
    ed3l.sdo_write(0x1c13, 0, map_1c13_bytes, True)
class OutputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('controlword', ctypes.c_int16),
        ('target_velocity', ctypes.c_int32),
    ]
class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('statusword', ctypes.c_uint16),
        ('velocity_actual_value', ctypes.c_int32),
        ('torque_actual_value', ctypes.c_int16),
    ]
            ...
            if master.state == pysoem.OP_STATE:
                output_data = OutputPdo()
                output_data.target_velocity = 500  # RPM
                for control_cmd in [6, 7, 15]:
                    output_data.controlword = control_cmd
                    ed3l.output = bytes(output_data)  # that is the actual change of the PDO output data
                    master.send_processdata()
                    master.receive_processdata(1_000)
                    time.sleep(0.01)
                try:
                    while 1:
                        master.send_processdata()
                        master.receive_processdata(1_000)
                        print(convert_input_data(ed3l.input).statusword)
                        time.sleep(0.01)
                except KeyboardInterrupt:
                    print('stopped')
                # zero everything
                ed3l.output = bytes(len(ed3l.output))
                master.send_processdata()
                master.receive_processdata(1_000)
            else:
                print('failed to got to op state')
            ...
Xubunt commented 1 year ago

After running the code with your changes, I get ValueError: Buffer size too small (6 instead of at least 8 bytes) It seems this is an issue with specifying the size of the bytes when using the struct module?

bnjmnp commented 1 year ago

I did a slight update of my code from the last comment. Not sure if it helps. Could you please post the complete stack trace of the error?

Xubunt commented 1 year ago

Re-ran and still got same error.

Xubunt commented 1 year ago

When I run the code while the motor is hooked up to ESView, I get this error: image

bnjmnp commented 1 year ago

That is strange, you could try to cut the input PDO struct to 6 byte:

class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('statusword', ctypes.c_uint16),
        ('velocity_actual_value', ctypes.c_int32),
    ]

According to the ESI file it should have 8 byte.

Xubunt commented 1 year ago

That got rid of the buffer error, thanks! However, I now have another issue. When I run the code with the process data loop, I get this error:

incorrect wkc
incorrect wkc
incorrect wkc
1560
incorrect wkc
1560
incorrect wkc
1560
incorrect wkc
1560
incorrect wkc
1560
incorrect wkc
1560

When I run the code without the process data loop, I get this error:

failed to got to op state

For reference, the code with the process data loop is in my original post, the first code. The code without the process data loop is below:

import struct

import pysoem
import time
import ctypes

import threading

ed3l = None

class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('statusword', ctypes.c_uint16),
        ('velocity_actual_value', ctypes.c_int32),
    ]

class OutputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('controlword', ctypes.c_int16),
        ('target_velocity', ctypes.c_int32),
    ]

modes_of_operation = {
    'No mode': 0,
    'Profile position mode': 1,
    'Profile velocity mode': 3,
    'Homing mode': 6,
    'Cyclic synchronous position mode': 8,
    'Cyclic synchronous velocity mode': 9,
    'Cyclic synchronous torque mode': 10,
}

def convert_input_data(data):
    return InputPdo.from_buffer_copy(data)

pd_thread_stop_event = threading.Event()
master = pysoem.Master()
master.open('eth1')
actual_wkc = 0
def ed3l_config_func(slave_pos):
    global ed3l

    map_1c12_bytes = struct.pack('BxH', 1, 0x1600)
    ed3l.sdo_write(0x1c12, 0, map_1c12_bytes, True)

    map_1c13_bytes = struct.pack('BxH', 1, 0x1A00)
    ed3l.sdo_write(0x1c13, 0, map_1c13_bytes, True)

# def processdata_thread():
#     global master  # not sure if this is necessary
#     global pd_thread_stop_event  # not sure if this is necessary
#     global actual_wkc  # not sure if this is necessary
#     while not pd_thread_stop_event.is_set():
#         master.send_processdata()
#         actual_wkc = master.receive_processdata(10000)
#         if not actual_wkc == master.expected_wkc:
#             print('incorrect wkc')
#         time.sleep(0.01)

def main():
    global ed3l
    master = pysoem.Master()
    master.open('eth1')  # someting like '\\Device\\NPF_{B4B7A38F-7DEB-43AF-B787B7-EABADE43978EA}' under Windows
    if master.config_init() > 0:
        ed3l = master.slaves[0]
        ed3l.config_func = ed3l_config_func
        master.config_map()
        if master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE:
            master.state = pysoem.OP_STATE
            # proc_thread = threading.Thread(target=processdata_thread)
            # proc_thread.start()
            # 
            # master.send_processdata()  # this is actually done in the "processdata_thread" - maybe it is not needed here
            # master.receive_processdata(
            #     2000)  # this is actually done in the "processdata_thread" - maybe it is not needed here

            master.write_state()
            master.state_check(pysoem.OP_STATE, 5_000_000)
            if master.state == pysoem.OP_STATE:
                output_data = OutputPdo()
                output_data.target_velocity = 500  # RPM
                for control_cmd in [6, 7, 15]:
                    output_data.controlword = control_cmd
                    ed3l.output = bytes(output_data)  # that is the actual change of the PDO output data
                    master.send_processdata()
                    master.receive_processdata(1_000)
                    time.sleep(0.01)
                try:
                    while 1:
                        master.send_processdata()
                        master.receive_processdata(1_000)
                        print(convert_input_data(ed3l.input).statusword)
                        time.sleep(0.01)
                except KeyboardInterrupt:
                    print('stopped')
                # zero everything
                ed3l.output = bytes(len(ed3l.output))
                master.send_processdata()
                master.receive_processdata(1_000)
            else:
                print('failed to got to op state')
        else:
            print('failed to got to safeop state')
        master.state = pysoem.PREOP_STATE
        master.write_state()
    else:
        print('no device found')
    master.close()

if __name__ == '__main__':
    main()
bnjmnp commented 1 year ago

Could be timing issues and maybe the drive you are using checks if process data is coming in precisely. You should also check in the main loop if the drive stays in OP_STATE or falls back to SAFEOP_STATE.

The thing is also that your device needs process data communication running (in parallel) wile going to OP_STATE, thus you cannot really get rid of the processdata_thread().

One thing you could try is, to increase the timeout given to receive_processdata(), maybe 100000 instead of 10000.

Xubunt commented 1 year ago

I upped to timeout, but still get the error:

Xubunt commented 1 year ago

Tried adding

Xubunt commented 1 year ago

I realize the numbers are the statuswords. What I am confused about it that the system is in OP state and the statusword is printing to terminal, but the motor is not moving. And it is because of the Overtime Watchdog error. Not sure if I can add a time.sleep somewhere to help the system catch up with the code or not - otherwise I am unsure of what to do

bnjmnp commented 1 year ago

Looks like your device requires a more rigorous/precise real time system. You could rather try to decrease the sleep of 0.01 after the master.receive_processdata(1_000) to 0.001 or removing it. You should also remove the print of the statusword, this slows down the loop a lot.

bnjmnp commented 1 year ago

Another thing you could try is to increase the watchdog timeouts with the set_watchdog() function. Check out #71, maybe that helps.

Xubunt commented 1 year ago

I did a few tweaks to the sleep and the watchdog error is gone. I get no alarms for when I run the script, but now the motor has an overtravel status when I run the script and it does not move. I have the force stop the script otherwise it keeps running. Here is the stack trace:

Xubunt commented 1 year ago

'

Xubunt commented 11 months ago

@bnjmnp any guidance is appreciated

Xubunt commented 11 months ago

'