bnjmnp / pysoem

Cython wrapper for the Simple Open EtherCAT Master Library
MIT License
96 stars 37 forks source link

I am using EK1100, EL2008 module. and I want to light the EL2008 with an LED. #64

Closed jehun0908 closed 7 months ago

jehun0908 commented 2 years ago

I am using EK1100, EL2008 module. I want to light the EL2008 with an LED. 1) In this case, do I need a safe_op or an open state? 2) Also, if necessary, is it operated in the order of PreOP, SafeOP, and OPstate? 3) If you print the values of print(master.state_check(pysoem.SAFEOP_STATE) and print(pysoem.SAFEOP_STATE), they are different by 2 and 4, what is the solution? 4) Please tell me what to do in the same situation as Question 3.

my Code

`import sys import time import pysoem import chardet import ctypes

master = pysoem.Master() master.open('NPF_{F0A11C28-9838-4230-8341-3FA8D32CD718}') print("EtherCAT master created and started...")

if master.config_init() > 0:

for slave in master.slaves:
    print('{}:'.format(slave.name))                       
    print(chardet.detect(slave.name.encode()))            
    print("--------slavename--------")
master.config_map()

slave0 = master.slaves[0]
slave1 = master.slaves[1]
if master.state_check(pysoem.SAFEOP_STATE, 50000) == pysoem.SAFEOP_STATE :
    master.state = pysoem.OP_STATE
    master.write_state()
    master.state_check(pysoem.OP_STATE,5000000)
    if master.state == pysoem.OP_STATE:
        slave1.sdo_write(0x7050, 1, bytes(ctypes.c_bool(1)))
        master.send_processdata()                         # send Data 
        master.receive_processdata(1000)                  # receive Data 
        time.sleep(0.01)
        try:
            while 1:
                master.send_processdata()                 # send Data loop
                master.receive_processdata(1_000)         # receive Data loop
                time.sleep(0.01)
        except KeyboardInterrupt:
            print('stopped')

        # zero everything
        master.send_processdata()                         # send Data 
        master.receive_processdata(1000)                  # receive Data 
    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()

print("------------------------------------End--------------------------------")`

bnjmnp commented 2 years ago

I think OP state is necessary to change the states of outputs. You probably also need to toggle the output via PDO communication like in the basic_example.py. PreOP, SafeOP and then OP state is the right sequence, yes.

jehun0908 commented 2 years ago

I turned on the LED referring to basic_example.py. This is a reference code for other people.

import sys
import struct
import time
import threading

from collections import namedtuple

import pysoem

class BasicExample:

    BECKHOFF_VENDOR_ID = 0x0002
    EK1100_PRODUCT_CODE = 0x044c2c52
    EL2008_PRODUCT_CODE = 0x7d83052

    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('EL2008', self.EL2008_PRODUCT_CODE, None)}

    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[1].output)

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

        toggle = True
        try:
            while 1:
                if toggle:
                    tmp[0] = 0x00
                else:
                    tmp[0] = 0xFF
                self._master.slaves[1].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, 50000) != 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()

        # send one valid process data to make outputs in slaves happy
        self._master.send_processdata()
        self._master.receive_processdata(2000)
        # request OP state for all slaves

        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')

    ifname = '[Your Device Name]'

    try:
        BasicExample(ifname).run()
    except BasicExampleError as expt:
        print('basic_example failed: ' + expt.message)
        sys.exit(1)
jehun0908 commented 2 years ago

I'd like to make a motor move separately from the above code. I faced one difficulty.

if master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE: 

If you print from the code inside "",

Master.state_check(pysoem.SAFEOP_STATE) outputs 2 values, and pysoem.SAFEOP_STATE outputs 4 values. So, we are not able to enter the SAFEOP state. Can you help me?

bnjmnp commented 2 years ago

BTW thiw is the way to generate code blocks on GitHub:

```py
def foo():
    pass
```

This will result in syntax highlighted code blocks:

def foo():
    pass
bnjmnp commented 2 years ago

In #30 there is an example that brings a motor of a servo drive to move. If you cannot reach SafeOP state, there is probably an configuration issue, it is good to read out the AL status code like in the minimal_example.py

jehun0908 commented 2 years ago

I looked at #30 and referred to it. Al status code indicates 0x0 (No error) I cannot reach SafeOP state.....

import pysoem
import time
import ctypes
import struct

KINBT11 = None

class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('Statusword', ctypes.c_uint16),
        ('Actual_Postion', ctypes.c_int32),
        ('Actual_Velocity', ctypes.c_int32),
        ('Digital_Input', ctypes.c_uint16),
    ]

class OutputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('Target_Velocity', ctypes.c_int32),
        ('Controlword', ctypes.c_uint16),
    ]

def config_func(slave_pos):
    global KINBT11

    # encoder increments
    KINBT11.sdo_write(0x608F, 1, bytes(ctypes.c_uint32(500)))
    # Motor revolutions
    KINBT11.sdo_write(0x608F, 2, bytes(ctypes.c_uint32(1)))

    # Max profile velocity
    KINBT11.sdo_write(0x607F, 0, bytes(ctypes.c_uint32(8000)))
    # Max motor speed
    KINBT11.sdo_write(0x6080, 0, bytes(ctypes.c_uint32(8000)))

    # # gear ratio
    KINBT11.sdo_write(0x6091, 1, bytes(ctypes.c_uint32(1)))
    KINBT11.sdo_write(0x6091, 2, bytes(ctypes.c_uint32(91)))    

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

def main():
    global KINBT11
    master = pysoem.Master()
    master.open('[My device name]')                       
    if master.config_init() > 0:                                                    
        KINBT11 = master.slaves[0]                                                
        KINBT11.config_func = config_func                                 
        master.config_map()                                                        
        print(master.state_check(pysoem.SAFEOP_STATE,50000))                         #### The value appears as 2.
        print(pysoem.SAFEOP_STATE)                                                   #### The value appears as 4.
        if master.state_check(pysoem.SAFEOP_STATE, 50000) == pysoem.SAFEOP_STATE:    # From here, we keep going to the else 
            print("True")
            master.state = pysoem.OP_STATE                                         
            master.write_state()                                                   
            master.state_check(pysoem.OP_STATE, 5000000)                          
            if master.state == pysoem.OP_STATE:                                    
                output_data = OutputPdo()                                           
                output_data.Target_Velocity = 500                                      
                output_data.Controlword = 15                                 
                KINBT11.output = bytes(output_data)                   
                master.send_processdata()                                     
                master.receive_processdata(1000)                              
                time.sleep(0.01)
                try:
                    while 1:
                        master.send_processdata()                                
                        master.receive_processdata(1000)                         
                        print(convert_input_data(KINBT11.input).Target_Velocity)
                        print(convert_input_data(KINBT11.input).Statusword)   
                        time.sleep(0.01)
                except KeyboardInterrupt:
                    print('stopped')
                # zero everything 
                KINBT11.output = bytes(len(KINBT11.output))
                master.send_processdata()                                         
                master.receive_processdata(1000)                                
            else:
                print('failed to got to op state')
                print('al status code {} ({})'.format(hex(KINBT11.al_status), pysoem.al_status_code_to_string(KINBT11.al_status)))
        else:
            print('failed to got to safeop state')
            print('al status code {} ({})'.format(hex(KINBT11.al_status), pysoem.al_status_code_to_string(KINBT11.al_status)))
        master.state = pysoem.PREOP_STATE
        master.write_state()
    else:
        print('no device found')
    master.close()

if __name__ == '__main__':
    main()

RxPDO TxPDO

bnjmnp commented 2 years ago

You probably tried to increase the timeout for the state_check? And you are sure the KINBT11 is the only device in the EtherCAT network?

jehun0908 commented 2 years ago
  1. I tried increasing "Timeout of State_check" but there was no change.
  2. The KINBT11 driver is a driver created using the ET1100 and works well using the TwinCAT program. But I want to use Python to operate it.
  3. This is the only driver I have.
if master.state_check(pysoem.SAFEOP_STATE, 50000) == pysoem.SAFEOP_STATE:

When outputting the value "if master.state_check(pysoem.SAFEOP_STATE, 50000" is 2 and go to the else statement.

jehun0908 commented 2 years ago

Accept 0x1A02 value from inputPdo instead of default 0x1A00. Where should I add this part? Can there be an error because of this part?

0x1A02_InputPDO

bnjmnp commented 2 years ago

Can you please also take a screenshot of the Startup tab? There you will see what sdo_writes are needed in the config function.

jehun0908 commented 2 years ago

I'm not sure if the picture of start_tab you want is correct.

Start_tab

bnjmnp commented 2 years ago

The one next to this one, on the left.

jehun0908 commented 2 years ago

Oh! Startup_Tab is here.

Startup_tab

bnjmnp commented 2 years ago

Right, this is what TwinCAT is doing at the transition from PreOP to SafeOP state.

The equivalent place to put that is in the "config function", because the code there is also called in the transition from PreOP to SafeOP (during the config_map())

You could do that ether this way:

def config_func(slave_pos):
    global KINBT11
    rx_map_obj_bytes = struct.pack('BxH', 1, 0x1602)
    KINBT11.sdo_write(0x1c12, 0, rx_map_obj_bytes, True)
    tx_map_obj_bytes = struct.pack('BxH', 1, 0x1A02)
    KINBT11.sdo_write(0x1c13, 0, tx_map_obj_bytes, True)

or that way:

def config_func(slave_pos):
    global KINBT11
    KINBT11.sdo_write(0x1c12, 0, bytes([0x01, 0x00, 0x02, 0x16]), True)
    KINBT11.sdo_write(0x1c13, 0, bytes([0x01, 0x00, 0x02, 0x1A]), True)

Not sure if this is 100% correct. I'm also not sure it this will solve the problem, but it is worth to try it.

jehun0908 commented 2 years ago

Thank you for your time on my issue. I changed it according to your advice. But there is still a problem. The result value of the code below is still 2. I am interpreting "master.state_check(pysoem.SAFEOP_STATE, 50000)" as waiting for the master to reach the state of "SAFEOP_STATE"

Is there a way to do it without the code below?

master.state_check(pysoem.SAFEOP_STATE, 50000) == pysoem.SAFEOP_STATE
jehun0908 commented 2 years ago

Accessing COE objects with sdo_write and sdo_read works fine. But I still can't change to SAFEOP_STATE.

jehun0908 commented 2 years ago

oh.. I thought the computer read the COE without any problems. However, if you try to read coe after config_map(), an error is printed. Is this because of the error of misreading eeprom?

bnjmnp commented 2 years ago

Could you post the error message? Maybe the drive also needs DC (Distributed Clocks) configured. You can check the DC tab in TwinCAT or take a look at the ESI file.

jehun0908 commented 2 years ago

This is the error message and DC tab you mentioned. max_motor_speed1 is output. However, an error occurs when outputting again after config_map().

coe_read_error

dc_tab

bnjmnp commented 2 years ago

Okay DC is not needed, this is fine.

For some reaseon the drive issues an Emergency Error. The 40960 is hex 0xA000. You need to check the manual what might cause this Emergency Error or ask the vendor of you device.

jehun0908 commented 2 years ago

Thank you for your advice. I will look at the manual and ask the supplier according to your advice. There are still two questions left.

  1. Is it right that "config_map()" does not affect sdo_read?
  2. Is there a high probability that "master.state_check(pysoem.SAFEOP_STATE,50000)" represents a value of 2 because of the device?
bnjmnp commented 1 year ago

It has been a while. Sorry for not answering to your last post, somehow I lost track of this conversation. Do you still have issues with this? Did you had any success in the end?

bnjmnp commented 7 months ago

Closed due to inactivity of the OP. Please reopen the ticket if you need further help on this.