bnjmnp / pysoem

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

can't write to pdo #100

Closed 2000dB closed 1 year ago

2000dB commented 1 year ago

Hello and thank you for this library!

My issue is most likely device specific and due to my limited experience with ethercat, but this seems like a good place to ask anyway. I have a voice coil actuator from DH Robotics for which I have the ESI file. I've parse what I could from that file and I have SDO communications working well enough and I can read from the PDO. However when I try to write it doesn't seem to make its way to the device. Am I missing something with the config_map() perhaps, but I'm not quite sure what registers I need to configure.

See script below based on one of your examples and ESI file attached.

Pointer welcome and appreciated, thank you!

import sys
import struct
import time
import collections
import dataclasses
import typing
import threading
import ctypes

import pysoem

dh_r = None
dh_z = None

class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('status_word', ctypes.c_uint16),
        ('position_actual_value', ctypes.c_int32),
        ('velocity_actual_value', ctypes.c_int32),
        ('torque_actual_value', ctypes.c_int16),
        ('modes_of_operation', ctypes.c_int8),
        ('complement', ctypes.c_int8)
    ]

class OutputPdo(ctypes.Structure):
    _pack_ = 1
    _field_ = [
        ('control_word', ctypes.c_uint16),
        ('target_position', ctypes.c_int32),
        ('velocity_offset', ctypes.c_int32),
        ('torque_offset', ctypes.c_int16),
        ('target_velocity', ctypes.c_int32),
        ('target_torque', ctypes.c_int16),
        ('physical_outputs', ctypes.c_uint32),
        ('modes_of_operation', ctypes.c_int8),
        ('complement', ctypes.c_int8)
    ]

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)

def main():
    try:
        master = pysoem.Master()
        master.open('\\Device\\NPF_{820F2256-4767-4E8D-907F-AADB90D50B3A}')
        if master.config_init() > 0:
            dh_r = master.slaves[0]
            dh_z = master.slaves[1]

        master.config_map()

        if master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE:
            master.state = pysoem.OP_STATE
            master.write_state()
            master.send_processdata()

            master.state_check(pysoem.OP_STATE, 50_000)
            if master.state != pysoem.OP_STATE:
                master.read_state()
                for slave in 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)))

            dh_r_out = OutputPdo()
            dh_r_out.modes_of_operation = modes_of_operation['Profile velocity mode']
            dh_r_out.target_velocity = 1000

            for control_cmd in [6,7,15]:
                dh_r_out.controlword = control_cmd
                master.slaves[0].output = bytes(dh_r_out)  
                master.send_processdata()
                master.receive_processdata(1_000)
                time.sleep(0.1)

            try:

                while 1:
                    master.send_processdata()
                    master.receive_processdata(1_000)
                    dh_r_in = convert_input_data(master.slaves[0].input)
                    print(dh_r_in.position_actual_value) 
                    time.sleep(0.1)
            except KeyboardInterrupt:
                dh_r.sdo_write(0x6040, 0, bytes(ctypes.c_int32(6)))
                dh_z.sdo_write(0x6040, 0, bytes(ctypes.c_int32(6)))

                print('stopped')

    except Exception as expt:
        print(expt)
        sys.exit(1)

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

Hi. I can't find the ESI file. With it I might be easier to help you... maybe you can also provide a link to the product website.

2000dB commented 1 year ago

Hi,

Yes of course should have included that in the first place. Product is VLAR-20-15 ESI

Thank you!

bnjmnp commented 1 year ago

Did you have any progress on this?

In your InputPdo you missed the "digital_inputs":

class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('status_word', ctypes.c_uint16),
        ('position_actual_value', ctypes.c_int32),
        ('velocity_actual_value', ctypes.c_int32),
        ('torque_actual_value', ctypes.c_int16),
        ('digital_inputs', ctypes.c_uint32),
        ('modes_of_operation', ctypes.c_int8),
        ('complement', ctypes.c_int8)
    ]

But I don't think this is causing to much issues.

In the ESI I saw this:

            <InitCmd>
                <Transition>PS</Transition>
                <Index>#x6060</Index>
                <SubIndex>0</SubIndex>
                <Data>08</Data>
                <Comment>Op mode</Comment>
            </InitCmd>
            <InitCmd>
                <Transition>PS</Transition>
                <Index>#x60C2</Index>
                <SubIndex>1</SubIndex>
                <Data>02</Data>
                <Comment>Cycle time</Comment>
            </InitCmd>

You may need to set thees objects during the transition from PreOP to SafeOP.

This could be accomplished by adding a config function to the master.slave[i].config_func like in the basic_example.py

2000dB commented 1 year ago

Hi yes I got it to work, but turns out it was just a typo in my OutputPDO struct... I think the ESI the vendor gave me is also plain wrong as I had to remove some fields on both InputPDO and OutputPDO.

Appreciate your help!