Code Monkey home page Code Monkey logo

pysoem's Introduction

PySOEM

PySOEM is a Cython wrapper for the Simple Open EtherCAT Master Library (https://github.com/OpenEtherCATsociety/SOEM).

Introduction

PySOEM enables basic system testing of EtherCAT slave devices with Python.

Features

  • input process data read and output process data write
  • SDO read and write
  • EEPROM read and write
  • FoE read and write

Todo

  • EoE

Beware that real-time applications need some special considerations.

Requirements

Linux

  • Python 3
  • Python scripts that use PySOEM must be executed under administrator privileges

Windows

[*]Make sure you check "Install Npcap in WinPcap API-compatible Mode" during the install

macOS (new with PySOEM 1.1.5)

  • Python 3

Installation

python -m pip install pysoem

or

pip install pysoem

Consider using a virtualenv.

Usage

Although there are some pieces missing, the documentation is hosted on "Read the Docs" at: pysoem.readthedocs.io.

Please also have a look at the examples on GitHub.

Contribution

Any contributions are welcome and highly appreciated. Let's discuss any (major) API change, or large piles of new code first. Using this pysoem chat room on gitter is one communication channel option.

Changes

v1.1.6

  • Adds working counter check on SDO read and write.
  • Fixes issues with config_init() when it's called multiple times.

v1.1.5

  • Adds support for redundancy mode, master.open() provides now an optional second parameter for the redundancy port.

v1.1.4

  • Fixes Cython compiling issues.

v1.1.3

  • Adds function _disable_complete_access() that stops config_map() from using "complete access" for SDO requests.

v1.1.0

  • Changed the data type for the name attribute of SDO info CdefCoeObject and CdefCoeObjectEntry, they are of type bytes now instead of a regular Python 3 string.
  • Also changed the desc attribute of the find_adapters() list elements to bytes.
  • Introduces the open() context manager function.
  • Adds the setup_func that will maybe later replace the config_func.

v1.0.8

  • Version bump only to re-upload to PyPI with windows-wheel for Python 3.11

v1.0.7

  • Fix issues with timeouts at amend_mbx and set_watchdog.

v1.0.6

  • Introduces amend_mbx and set_watchdog, though this is rather experimental
  • New example firmware_update.py.

v1.0.5

  • Introduces the manual_state_change property

v1.0.4

  • Proper logging
  • Introduces mbx_receive

v1.0.3

  • Fix the FoE password issue

v1.0.2

  • Licence change to MIT licence
  • Introduces configurable timeouts for SDO read and SDO write
  • Improved API docs

v1.0.1

  • API change: remove the size parameter for foe_write
  • Introduces overlap map support

v1.0.0

  • No Cython required to install the package from the source distribution

v0.1.1

  • Introduces FoE

v0.1.0

  • Update of the underlying SOEM

v0.0.18

  • Fixes bug when Ibytes = 0 and Ibits > 0

v0.0.17

  • Exposes ec_DCtime (dc_time) for DC synchronization

v0.0.16

  • Improvement on SDO Aborts

v0.0.15

  • SDO info read

v0.0.14

  • Readme update only

v0.0.13

  • Initial publication

pysoem's People

Contributors

alexvinarskis avatar askogvold-halodi avatar bnjmnp avatar gullinbustin avatar jeremyshubert avatar lukas-beckmann avatar martinall91 avatar sr4ven avatar tbrckmnn avatar zchen24 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pysoem's Issues

Slave not reached OP state and incorrect wkc

EL1008: input slave
EL2809: output slave

Below is the following script :

import sys
import struct
import time
import threading
from collections import namedtuple

import pysoem

class BasicExample:

BECKHOFF_VENDOR_ID = 0x0002
EK1100_PRODUCT_CODE = 0x044c2c52
EL2809_PRODUCT_CODE = 0x0AF93052
EL1008_PRODUCT_CODE = 0x03F03052
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('EL2809', self.EL2809_PRODUCT_CODE, None),
						          2: SlaveSet('EL1008', self.EL1008_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)

	print(output_len)
	tmp = bytearray([0 for i in range(output_len)])
	print(tmp)
	toggle = True
	try:
		while 1:
			tmp[0] = 0x01
			tmp[1] = 0x10
			self._master.slaves[1].output = bytes(tmp)
			time.sleep(1)
	except KeyboardInterrupt:
		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()
	self._master.config_dc()

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

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

	if all_slaves_reached_op_state:
		self._pdo_update_loop()
		#self._pdo_update_loop1()

	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)

How to use

Hi folks, im new in Raspberry and etherchat.

I cloned it on my raspberry and try to run it, but when i run setup.py i get a message on terminal.

sage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

error: no commands supplied

My goal is use this lib to connect a raspberry with an ethercat slave machine.

Can someone help me, to run it?

Problem installing PySOEM on Win7

Hey,

I'm trying to install PySOEM on my Win 7 machine but I get the following error:

LINK : fatal error LNK1181: Eingabedatei "wpcap.lib" kann nicht geöffnet werden.
error: command 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.14.26428\bin\HostX86\x86\link.exe' failed with exit status 1181

Currently I have the newest version of Npcap installed, but before I tried the latest version of Winpcap with the same result.
I installed the package Cython and I have Visual Studio 2017 installed,

Do you have anything in mind how to solve this problem?

Thanks in advance and kind regards,
Marc

about the incorrect wkc

I try to use two functions, one does the setup and another does the teardown.

def setup():
    ...
    self.check_thread.start()
    self.proc_thread.start()
    ...
def teardown():
    ...
    self.check_thread_stop_event.set()
    self.check_thread_stop_event.set()
    self.proc_thread.join()
    self.check_thread.join()
    ...

Sometimes I call the teardown, it successfully stops the thread and closes everything. But sometimes it prints 'incorrect wkc',
I checked the actual wkc is -1 or 0, and the expected is 9.

Does that mean the teardown is already close to the master, but the pdo_thread is still running?

How to override default write to 0x1c13 on device init?

Hi, I'm having good luck using this library to talk to a few EK1100 and EL4102 units - thank you very much for making such readable and accessible software!

I'm basically adapting the basic_example.py script to my needs and this works great with the EL4102 (a 2-channel analog output device). I don't need to do anything in a setup function, I can just send output values every pdo cycle.

However, I'm having trouble adding an 8-channel device (EL4008) from the same family to this setup. When the script gets to lf._master.config_map() it fails with the error:

BasicExample(sys.argv[1]).run()
  File "/home/pi/akatesting/ethercatscratch/aka_basic_example.py", line 164, in run
    self._master.config_map()
  File "pysoem/pysoem.pyx", line 228, in pysoem.pysoem.CdefMaster.config_map
pysoem.pysoem.ConfigMapError: [SdoError(3, 7187, 1, 101253137, 'Subindex does not exist')]

...from what I understand of 'SdoError' structure here, this means that in the normal course of config_map the master tries to write to slave 3 (the EL4008, in my setup - if I switch the installation order the "3" changes accordingly) at the address 0x1c13, which would definitely fail with "Subindex does not exist" because that is true for EL4008.

I'm confused by this because in other examples online (the very helpful https://github.com/ngblume/ethercat-pysoem/blob/master/separate_thread/separate_thread.py for example) there is no indication of EL4008 needing special treatment on startup. Have others had luck with recent builds of SOEM and pySOEM nd the EL4008? I am wondering if perhaps the underlying SOEM library has changed how it initializes devices?

Many thanks for any help you can provide - for what it's worth my script is here: https://github.com/AKAMEDIASYSTEM/ethercatscratch/blob/master/aka_basic_example.py

Windows 10 download issue

Hello @bnjmnp!
I have a Beckhoff EK1100 module that I would love to use the PySOEM module for. I am experiencing issues installing the module via pip. I am receiving the error: "LINK : fatal error LNK1181: cannot open input file 'wpcap.lib' ". I believe this is because I don't have WinPcap downloaded, as it is not supported on Windows 10. I tried Npcap in WinPcap compatibility mode, but this did not fix the issue. Do you have any suggestions? Thank you for your help!

Failed to go to OP STATE with MR-J4-70TM-LD servo amplifier

Hello @bnjmnp
We are trying to set up 4 Mitsubishi HG-KR73 motors controlled by SMC drivers (model LECSN2-T9, AC servo MR-J4 -70TM-LD), equipped with EtherCAT cards (https://www.accs.cz/Files/FA/SERVO/MR-J4-TM_Instr_Manual_EtherCAT.pdf , this is the manual we are using)

We have installed Pysoem on a Raspberry Pi4 and we have tried many different codes. We are able to write and read on the drivers and to put them in Safe Op state, but we never succeeded in movig the motors, neither we have reached the OP state.

For instance, we are trying to run the following code, which we adapted from your one in Issue #30

import pysoem
import ctypes
import time

master = pysoem.Master()
master.open('eth0')

def config_func():
    global device
    #software position limit
    device.sdo_write(0x607D, 1, bytes(ctypes.c_int32(0)))
    device.sdo_write(0x607D, 2, bytes(ctypes.c_int32(1000)))

    # gear ratio
    device.sdo_write(0x6091, 1, bytes(ctypes.c_uint32(1000000)))

    #polarity
    device.sdo_write(0x607E, 0, bytes(ctypes.c_uint8(192)))


if master.config_init() > 0:
    device = master.slaves[0]
    device.config_func=config_func()
    master.config_map()
    
    if master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE:
        master.state = pysoem.OP_STATE
        master.write_state()
        master.state_check(pysoem.OP_STATE, 5_000_000)
        
        
        
        if master.state == pysoem.OP_STATE:
            device.sdo_write(0x6060, 0, bytes(ctypes.c_int8(1)))
            device.sdo_write(0x607A, 0, bytes(ctypes.c_int32(100))) #target position

            
            for control_cmd in [6, 7, 15]:
                device.sdo_write(0x6040, 0, bytes(ctypes.c_uint16(control_cmd)))
                master.send_processdata()
                master.receive_processdata(1_000)
                time.sleep(0.01)
            
            try:
                while 1:
                    master.send_processdata()
                    master.receive_processdata(1_000)
                    time.sleep(0.01)
            except KeyboardInterrupt:
                print('stopped')
            # zero everything
            device.output = bytes(len(device.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()

The answer we get is 'failed to got to OP_STATE'

Any help is welcomed.

SDO accesses interfere PDO update loop?

I'm writing a motor control program based on basic_example.py.
Sometimes I get a sync manager watchdog error when I make SDO accesses while running a PDO update loop.
I guess this is because the SDO access function interferes with the PDO update loop thread by the GIL.
I think it can be solved by using a non-blocking SDO access function or by separating the PDO update loop and SDO access into two processes.
However, both seem to be difficult with the current pysoem.

Thank you.

Timing of Send_Processdata and Receive_Processdata

Another issue / question regarding understanding of timing:

How does the timing between iterations of Send_Processdata and Receive_Processdata work?

In my code (https://github.com/ngblume/ethercat-pysoem/tree/master/separate_thread) I see stable outputs, when running a separate task every 0.01 s (as per your example).
When changing that to every 0.1 s the ouputs work correctly (set and stay), while the input is always read as FALSE.

I still get the feeling, that these some basic EtherCAT timing thing, that I simply haven't understood.
Could you point me in the right direction ?
Thanks !

Cheers
Niels

error when running setup.py

Hello @bnjmnp, I cloned pysoem into my directory and cloned the soem files into the soem folder inside pysoem.
I ran python3 setup.py build_ext --inplace but I got an error:
error: command 'i686-linux-gnu-gcc' failed with exit status 1.

Below is the message I get after running the setup.py command.

running build_ext building 'pysoem.pysoem' extension i686-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I./pysoem -I./soem/oshw/linux -I./soem/osal/linux -I./soem/oshw -I./soem/osal -I./soem/soem -I/usr/include/python3.5m -c pysoem/pysoem.c -o build/temp.linux-i686-3.5/pysoem/pysoem.o In file included from ./soem/soem/ethercat.h:18:0, from pysoem/pysoem.c:633: ./soem/soem/ethercatdc.h:20:1: warning: function declaration isn’t a prototype [-Wstrict-prototypes] boolean ec_configdc(); ^ In file included from /usr/include/python3.5m/pytime.h:6:0, from /usr/include/python3.5m/Python.h:65, from pysoem/pysoem.c:40: pysoem/pysoem.c: In function ‘__pyx_pf_6pysoem_6pysoem_9CdefSlave_28_get_PO2SOconfig’: pysoem/pysoem.c:8440:52: error: ‘ec_slavet {aka struct ec_slave}’ has no member named ‘user’ __Pyx_INCREF(((PyObject *)__pyx_v_self->_ec_slave->user)); ^ /usr/include/python3.5m/object.h:779:19: note: in definition of macro ‘Py_INCREF’ ((PyObject *)(op))->ob_refcnt++) ^ pysoem/pysoem.c:8440:3: note: in expansion of macro ‘__Pyx_INCREF’ __Pyx_INCREF(((PyObject *)__pyx_v_self->_ec_slave->user)); ^ pysoem/pysoem.c:8441:49: error: ‘ec_slavet {aka struct ec_slave}’ has no member named ‘user’ __pyx_r = ((PyObject *)__pyx_v_self->_ec_slave->user); ^ pysoem/pysoem.c: In function ‘__pyx_pf_6pysoem_6pysoem_9CdefSlave_30_set_PO2SOconfig’: pysoem/pysoem.c:8507:26: error: ‘ec_slavet {aka struct ec_slave}’ has no member named ‘user’ __pyx_v_self->_ec_slave->user = ((void *)__pyx_v_self->_cd); ^ pysoem/pysoem.c:8547:42: warning: assignment from incompatible pointer type [-Wincompatible-pointer-types] __pyx_v_self->_ec_slave->PO2SOconfig = __pyx_f_6pysoem_6pysoem__xPO2SOconfi ^ error: command 'i686-linux-gnu-gcc' failed with exit status 1

Please advise on how to proceed.
Thank you.

Setting up distributed clocks / Sync

I've some problems setting up synchronisation on a LAN 9252 eval board (EVB-LAN9252-Add-on).
I wrote some scan and connect functions which work well and I can also send some data to my PDOs (which are directly mapped to Input/Output ports on 0x1000 and 0x0F01 respectively). Now I want to implement a cyclic timer and don't get it to work properly. After setting up the sync in PREOP, I write to slave.dc_sync and can set/reset the port with some commands. Problem is, I really don't understand what I'm doing there :3
Can you explain some of the parameters of the dc_sync function and maybe give a small example on how to set up distributed clocks / syncs?

def scanDevices():
    adapters = {}
    for adapter in pysoem.find_adapters():
        master = pysoem.Master()
        master.open(adapter.name)
        master.config_init()
        adapters[adapter.name] = len(master.slaves)
        master.close()
    return adapters

def connectDevice():
    adapters = scanDevices()
    adapterNames = [a for a, n in adapters.items() if n > 0]
    print(adapterNames)
    master = pysoem.Master()
    master.open(adapterNames[0])
    master.config_init()
    master.config_map()
    return master, master.slaves[0]

def changeOpState(master,state):
    master.state = state
    master.write_state()
    return master.state_check(state,5000) == state

def sendOutput(master,output):
    master.slaves[0].output = output
    master.send_processdata()
    wkc = master.receive_processdata()
    assert(master.expected_wkc == wkc)
    print(master.slaves[0].input)

master, slave = connectDevice()
changeOpState(master,pysoem.PREOP_STATE)
master.config_dc()
slave.dc_sync(1,10)        # sets SYNC1 high
slave.dc_sync(1,10000)  # sets SYNC1 low

EDIT:
Solved it myself:

slave.dc_sync(1,1000000)  

Sets the cycle time in nanoseconds, in this case every 1ms.
If I go too low, this won't work because of reasons (chip details).

CIA 402 engine

Hi,
i am using your library to communicate with a CIA 402 type engine. I can communicate with, retrieve the encoder value.
But impossible to make it turn. I think we should use processdata but I don't quite understand the principle.

Do you have an example of an engine or a bit of time to help me?

PySOES

Are there any plans to begin a PySOES project? If not, any recommendations of where to get started?

pysoem on windows 10 problem - will appreciate help

i am trying to use pysoem to emulate a master ethercat controller, but keep banging into a wall.

when i am trying to do pysoem.Master().open(name) i keep getting
"interface {24D8A70B-00A0-4643-81DE-C1D2DF72396D} could not open with pcap"
message, no matter which interface i am trying to use

this is for all the interfaces detected by pysoem.find_adapters()

also, i have installed win10pcap and wireshark, and they can access any interface

will appreciate any help

thanks

Noam

pdo output does not seem to work

I wrote a program to drive the motor with pysoem. It reached op state successfully but could only receive the data from the driver. If i try to send the control world to the driver, it doesn't work. Is this sth. wrong? Thanks for your help.

import sys
import pysoem
import struct
import time

class MotorDriver:

def __init__(self, ifname):
    self._ifname = ifname
    self._actual_wkc = 0
    self._master = pysoem.Master()
    self._master.in_op = False
    self._master.do_check_state = False

def pdo_map(self,pos):
    slave = self._master.slaves[pos]

    slave.sdo_write(0x1C12, 0, struct.pack('B', 0))
    slave.sdo_write(0x1C13, 0, struct.pack('B', 0))

    slave.sdo_write(0x1A02, 0, struct.pack('B', 0))
    slave.sdo_write(0x1A02, 1, struct.pack('I', 0x60410010))
    slave.sdo_write(0x1A02, 2, struct.pack('I', 0x60640020))
    slave.sdo_write(0x1A02, 0, struct.pack('B', 2))   

    slave.sdo_write(0x1602, 0, struct.pack('B', 0))
    slave.sdo_write(0x1602, 1, struct.pack('I', 0x60400010))
    slave.sdo_write(0x1602, 2, struct.pack('I', 0x607A0020))
    slave.sdo_write(0x1602, 0, struct.pack('B', 2))  

    slave.sdo_write(0x1C12, 1, struct.pack('H', 0x1602))
    slave.sdo_write(0x1C12, 0, struct.pack('B', 1))

    slave.sdo_write(0x1C13, 1, struct.pack('H', 0x1A02))
    slave.sdo_write(0x1C13, 0, struct.pack('B', 1)) 

def motor_run(self):    

    # Open EtherCAT master instance
    self._master.open(self._ifname)
    print("EtherCAT master created and started...")

    print("Enumarating slaves")
    # Enumerate and init all slaves
    if self._master.config_init() > 0:

        print("{} slaves found and configured".format(len(self._master.slaves))
 
        # PDO config 
        self.pdo_map(0)
        self.pdo_map(1)
        
        # PREOP_STATE to SAFEOP_STATE request - each slave's config_func is called
        self._master.config_map()
        print('Slaves mapped, state to SAFE_OP')

        # Read state of all slaves at start-up
        if self._master.state_check(pysoem.SAFEOP_STATE, 50000) != pysoem.SAFEOP_STATE:    
            self._master.read_state()
            for slave in self._master.slaves:
                if not slave.state == pysoem.SAFEOP_STATE:
                    print('{} did not reach SAFEOP state'.format(slave.name))
                    print('al status code {} ({})'.format(hex(slave.al_status),
                                                          pysoem.al_status_code_to_string(slave.al_status)))
            raise Exception('not all slaves reached SAFEOP state')

        self._master.state = pysoem.OP_STATE
        self._master.write_state()

        self._master.state_check(pysoem.OP_STATE, 50000)
        if self._master.state != pysoem.OP_STATE:
            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)))
            raise Exception('not all slaves reached OP state')

        try:

            self._master.slaves[0].output = struct.pack('H',0x06)
            self._master.slaves[1].output = struct.pack('H',0x06)
            self._master.send_processdata()
            time.sleep(0.001)
            self._master.slaves[0].output = struct.pack('H',0x07)
            self._master.slaves[1].output = struct.pack('H',0x07)
            self._master.send_processdata()
            time.sleep(0.001)
            self._master.slaves[0].output = struct.pack('H',0x0F)
            self._master.slaves[1].output = struct.pack('H',0x0F)
            self._master.send_processdata()
            time.sleep(0.001)

            while 1:
                # free run cycle
                self._master.send_processdata()
                self._master.receive_processdata(1000)
                
                rec1=self._master.slaves[0].input
                rec2=self._master.slaves[1].input
                
                p=2
                data1=struct.unpack_from('!i',rec1,p)
                data2=struct.unpack_from('!i',rec2,p)

                print('ac_ps:',data1,'    ','ac_ps:',data2)

                time.sleep(0.01)

        except KeyboardInterrupt:
            # ctrl-C abort handling
            print('stopped')
        self._master.state = pysoem.INIT_STATE
        # request INIT state for all slaves
        self._master.write_state()
        self._master.close()
    
    else:
        print('slaves not found')

if name == 'main':

print('script started')

if len(sys.argv) > 1:
    try:
        MotorDriver(sys.argv[1]).motor_run()
    except Exception as expt:
        print(expt)
        sys.exit(1)
else:
    print('give ifname as script argument')
    sys.exit(1)

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)

how to send NOP command frame

I'm testing the Kollmorgen AKD2G drive, but the pysoem can't work correctly with it. I captured the frame from PCMM (Kollmorgen EtherCAT master) to AKD2G drive, and I find that the PCMM sends NOP command periodically to keep something.

Can the pysoem send the NOP command? How to enable this?

Read/Write SDOs

Hey,

I managed to got the read_sdo_info example to run and get a "table" with entries like the following, which is correct:

Idx: 0x6005; Code: 9; Type: 42; BitSize: 40; Access: 0x0; Name: "Actual Setpoint"
Subindex 0; Type: 5; BitSize: 8; Access: 0x7 Name: "SubIndex 000"
Subindex 1; Type: 8; BitSize: 32; Access: 0xbf Name: "Actual Setpoint"

But now the question is how do I read and write and specific subindex?

import sys
import struct
import time
import threading

from collections import namedtuple

import pysoem

class BasicExample:

    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

    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

        print("Everything in OP.")

        output_len = len(self._master.slaves[0].output)

        print(output_len)

        try:
            while 1:
                # write here
                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):
            slave.is_lost = False

        print(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()

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

    try:
        BasicExample("\\Device\\NPF_{2A30A4C2-1510-4DE3-A4A5-62EC83B2BBCC}").run()
    except BasicExampleError as expt:
        print('basic_example failed: ' + expt.message)
        sys.exit(1)

I managed to get into the _pdo_update_loop but now I don't really know how to read/write some SDOs or PDOs.

EDIT:
I found how to read SDOs with the following commands:

                result = self._master.slaves[0].sdo_read(0x6005, 1, 4)
                print(result)

But the result I get is something like b'\x00\x00\xe0@' when it reads the number 7, is there any way to get the result as a decimal number?

Thanks in advance and kind regards
Marc

How to write PDO mapping config

We can use the default pdo mapping by this:

class OutputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ =[
        ('Control_Word',ctypes.c_uint16),
        ('Mode_Operation',ctypes.c_int8),
        ('Target_Position',ctypes.c_int32),
        ('Touch_probe_function',ctypes.c_uint16),
        ('physical_outputs',ctypes.c_uint32),
        ('bit_mask',ctypes.c_uint32)
    ]   

output_data = OutputPdo()

but how can we set the pdo mapping (write pdo mapping to device)?

I guess, use sdo to write to the address x1A00, x1600? but the pdos are lists of addresses, how to set the PDOS? so that i can select the pdos that i need instead of using the default.

How to log to console?

I'm sorry to have such a basic question, but I'm trying to step through the pyx file to determine where a particular sdo write is occurring (see #53), and I'm unable to get log messages to appear while I'm doing this.
If I'm running basic_example.py and I want to see all the log messages emitted by the .pyx file, what should I do? I've tried various ways that I usually involve adding something like logging.basicConfig(stream=sys.stdout, level=logging.INFO) to the head of my script (I've also added the same line to the head of the pyx file), but neither seem to have a result.
I don't usually get this far into the weeds when I use a module, so again my apologies if I'm asking a basic question here, I've searched for "how to see log messages from cython" and "how to see log messages from imported module" to no avail...
Thanks for any help/hint you can offer!

FoE Password Causes Overflow

A recent change causes the FoE password to result in an overflow error.
"OverflowError: Python int too large to convert to C long"
The password I need to implement is 4 bytes long and it worked in a previous version of pysoem.

Contributing, communicating

Hey there,

First of all I have to say that I really like this ported version of soem and I would like to discuss ways of contributing and also sharing ideas in the future.

Do you have any official channel like slack or gitter where we could get together and communicate with each other?

【need help】confused about the incorrect wkc

Sometimes the inconnect wkc occurs, and the check/recover fucntion useless.
I'm not clear about what make this error happen.
Could some one let me know the reason of 'inconnect wkc', that will help me a lot.
Thank you so much!

UnicodeDecodeError raised by umlauts in adapter drescription.

adapters.append(Adapter(_ec_adapter.name.decode('utf8'), _ec_adapter.desc.decode('utf8')))

Hello @bnjmnp,

when I call the find_adapters-function I get an UnicodeDecodeError exception, because of umlauts in the adapter drescription.
The adapter description might be encoded in ansi.

Is it possible to remove these decode calls of name and adapter description?
or make the encoding parameterizable, because of downward compatibility?

Thank you

Greetings from Langebrück ;)

Installation Fails (unable to open wpcap.lib) if using Python 3.10

I upgraded my python installation a few days ago to a new directory, and ran into this issue when installing pysoem.

C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\bin\HostX86\x64\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:.\soem\oshw\win32\wpcap\Lib\x64 /LIBPATH:C:\Users\*******\AppData\Local\Programs\Python\Python310\libs /LIBPATH:C:\Users\*******\AppData\Local\Programs\Python\Python310\PCbuild\amd64 /LIBPATH:C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\lib\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\ucrt\x64 /LIBPATH:C:\Program Files (x86)\Windows Kits\10\\lib\10.0.19041.0\\um\x64 wpcap.lib Packet.lib Ws2_32.lib Winmm.lib /EXPORT:PyInit_pysoem build\temp.win-amd64-3.10\Release\.\soem\osal\win32\osal.obj build\temp.win-amd64-3.10\Release\.\soem\oshw\win32\nicdrv.obj build\temp.win-amd64-3.10\Release\.\soem\oshw\win32\oshw.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatbase.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatcoe.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatconfig.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatdc.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatfoe.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatmain.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatprint.obj build\temp.win-amd64-3.10\Release\.\soem\soem\ethercatsoe.obj build\temp.win-amd64-3.10\Release\pysoem/pysoem.obj /OUT:build\lib.win-amd64-3.10\pysoem\pysoem.cp310-win_amd64.pyd /IMPLIB:build\temp.win-amd64-3.10\Release\.\soem\osal\win32\pysoem.cp310-win_amd64.lib
      LINK : fatal error LNK1181: cannot open input file 'wpcap.lib'
      error: command 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.31.31103\\bin\\HostX86\\x64\\link.exe' failed with exit code 1181
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for pysoem

I do have npcap installed in wpcap compatibility mode. I uninstalled my existing pysoem module in my python 3.9 directory and reinstalled it with no errors, so it seems restricted to the 3.10 version of python.

how `send_processdata()` and `receive_processdata()` work?

Hi @bnjmnp :
This is my first time contact with pysoem and soem,In the past few days,I have built communication with the driver (Panasonic),and make it run to a position successful with sdo_read() and sdo_write().But the basic_example.py and orther issues is all about send_processdata() and receive_processdata().and I found the command is in while cycle,how do I communicate with driver when the cycle began?and what's the different between sdo_read()sdo_write() written only and send_processdata()receive_processdata() used?
Maybe my question is junior,but it's helpful for me.
Thank you for taking your time.

sdo setting is written and can be read but it does not take effect

Hi @bnjmnp

Thanks for this repository. I have been able to setup and use several terminals without setting any SDOs and now I hit a wall:

I am trying to setup an EL3202-0010 for Pt temperature measurement from Beckhoff. I need to setup the 0x80n0:19 to 2. That changes the presets for a Pt100 sensor to a Pt1000 sensor. I can write the value and read from the terminal that it has been written, yet the setting does not take effect. It delivers the "correct" values for a Pt100 which is the default. I have done the sanity check of using TwinCAT to set it and it works... so there must be something missing in my code.

Here is in summary the initialization:

import pysoem
import struct

class BeckhoffInterface:

    def __init__(self):
        self.setup_interface()
        # terminal in PRE_OP state

        # set Pt1000 as RTD Element
        el3202_index = 1
        self.setup_EL3202(el3202_index)  # this does not take effect
        self.master.slaves[el3202_index].config_func = self.setup_EL3202 
        
        self.setup_terminals()
        # terminal in OPERATIONAL state

    def setup_interface(self):
        self.actual_wkc = 0
        self.do_check_state = False
        self.master = pysoem.Master("my-interface")
        self.master.open()
        self.master.config_init()

    def setup_terminals(self):
        self.master.config_map()
        timeout = 50000
        current_state = self.master.state_check(pysoem.SAFEOP_STATE, timeout)
        if current_state != pysoem.SAFEOP_STATE:
            self.master.close()
            raise RuntimeError("not all terminals reached SAFEOP state")

        # Request master and terminals to transit to operational state.
        self.master.state = pysoem.OP_STATE

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

        all_terminals_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_terminals_reached_op_state = True
                self.info("All terminals operational.")
                break
        self.master.in_operation = all_terminals_reached_op_state


    def setup_EL3202(self, terminal_index) -> None:
        rtd_elements = {"pt100": 0, "ni100": 1, "pt1000":2, "pt500":3, "pt200":4,
                        "Ni1000":5, "ni1000-tk5000":6, "ni120":7, "ohm_1/16":8,
                        "ohm_1/64":9}

        for channel in range(2):
            sdo_index = 0x8000 + 0x10 * channel
            sub_index = 19
            setting = struct.pack("<H", rtd_elements["pt1000"])
            self.master.slaves[terminal_index].sdo_write(sdo_index, sub_index, setting, True)
            self.master.slaves[terminal_index].dc_sync(1, 10000000) # is this necessary?

The code works

Any help is greatly appreciated!

the return of config_map() and send_overlap_processdata()

Hi @bnjmnp ,
config_map(): return IO map size (sum of all PDO in an out data).
Does that mean it returns the count of pdo mapping?
but it returns me the same number, after i sdo_write to 1601-1603 and 1A01-1A03, cuz i only need to use 1600 and 1A00.
I do the sdo_write in the config_func

def dev3_config_func(slave_pos):
    dev3.sdo_write(0x6083,0x0,bytes(ctypes.c_uint32(100000)))
    dev3.sdo_write(0x6084,0x0,bytes(ctypes.c_uint32(100000)))
    dev3.sdo_write(0x607F,0x0,bytes(ctypes.c_uint32(500000)))
    dev3.sdo_write(0x6080,0x0,bytes(ctypes.c_uint32(3000)))
    # not use pdos
    dev3.sdo_write(0x1601,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1602,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1603,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1A01,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1A02,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1A03,0x0,bytes(ctypes.c_uint16(0)))

dev1.config_func = dev1_config_func
dev2.config_func = dev2_config_func
dev3.config_func = dev3_config_func

print(master.config_map())

I know write pdos need to be done in pre op state. so i also tried to do

if master.state_check(pysoem.PREOP_STATE, 50_000) == pysoem.PREOP_STATE:
    dev1.sdo_write(0x1601,0x0,bytes(ctypes.c_uint16(0)))
    dev1.sdo_write(0x1602,0x0,bytes(ctypes.c_uint16(0)))
    dev1.sdo_write(0x1603,0x0,bytes(ctypes.c_uint16(0)))
    dev1.sdo_write(0x1A01,0x0,bytes(ctypes.c_uint16(0)))
    dev1.sdo_write(0x1A02,0x0,bytes(ctypes.c_uint16(0)))
    dev1.sdo_write(0x1A03,0x0,bytes(ctypes.c_uint16(0)))
    dev2.sdo_write(0x1601,0x0,bytes(ctypes.c_uint16(0)))
    dev2.sdo_write(0x1602,0x0,bytes(ctypes.c_uint16(0)))
    dev2.sdo_write(0x1603,0x0,bytes(ctypes.c_uint16(0)))
    dev2.sdo_write(0x1A01,0x0,bytes(ctypes.c_uint16(0)))
    dev2.sdo_write(0x1A02,0x0,bytes(ctypes.c_uint16(0)))
    dev2.sdo_write(0x1A03,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1601,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1602,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1603,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1A01,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1A02,0x0,bytes(ctypes.c_uint16(0)))
    dev3.sdo_write(0x1A03,0x0,bytes(ctypes.c_uint16(0)))
    
else:
    print('no in pre op')
dev1.config_func = dev1_config_func
dev2.config_func = dev2_config_func
dev3.config_func = dev3_config_func

master.config_map()

the return of master.config_map() is always 144, and the send_overlap_processdata() is always 172.
My purpose is to reduce the pdo data in every send and receive process.
Do you have any suggestions

send PDO incl. Index and SubIndex

Hello,
I'm working on a program to drive three motors using a driver card from Trinamic.
Had a look on issue #23 and moste of the part works well for me.
I was able to give the card the specifications I want with slave.sdo_write(Index,SubIndex,Value) and also could check the changes.

Now I need to give the card the commands to move the motors.
In the example at #23 self._master.slaves.output = Value was used.
At this point only the value will be send.
According to the documentation of Trinamic I also have to send the value with Index and SubIndex like sdo_write().

Would be great if you could give me some help!

Best regards

ISSUE about using 'hot_key' to start, stop and terminate the devices.

from pmm60_PDO_config import InputPdo
from pmm60_PDO_config import OutputPdo
from keyboard import add_hotkey
import time
import pysoem
import ctypes

class AM:
    def __init__(self,port):
        self.master = pysoem.Master()
        self.master.open(port)
        self.master.config_init()
        self.dev1 = self.master.slaves[0]
        self.dev2 = self.master.slaves[1]
        self.dev3 = self.master.slaves[2]
        self.dev4 = self.master.slaves[3]
        self.flag = 0
        
        def dev1_config(slave_pos):
            self.dev1.sdo_write(0x200E,0x09, bytes(ctypes.c_int32(50427137)))
        
        def dev2_config(slave_pos):
            self.dev2.sdo_write(0x200E,0x09, bytes(ctypes.c_int32(50427137)))
        
        def dev3_config(slave_pos):
            self.dev3.sdo_write(0x200E,0x09, bytes(ctypes.c_int32(50427137)))
            
        def dev4_config(slave_pos):
            self.dev4.sdo_write(0x200E,0x09, bytes(ctypes.c_int32(50427137)))

        self.dev1.config_func = dev1_config
        self.dev2.config_func = dev2_config
        self.dev3.config_func = dev3_config
        self.dev4.config_func = dev4_config
        
        self.master.config_map()
        
    def handle_pdo_process_out_loop(self):
        self.master.send_processdata()
        self.master.receive_processdata(1_000)
        
    def handle_pdo_process(self,type='in'):
        self.master.send_processdata()
        self.master.receive_processdata(1_000)
        time.sleep(0.01)
        print(type,time.time())
        # print(self.flag,self.convert_input_data(self.dev2.input).Status_word,self.convert_input_data(self.dev2.input).Position_actual_encoder_value)

    def setUp(self):
        if self.master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE:
            self.master.state = pysoem.OP_STATE    
    
            self.handle_pdo_process()
        
            self.master.write_state()
            self.master.state_check(pysoem.OP_STATE, 5_000_000)
            if self.master.state == pysoem.OP_STATE:
                self.output_data = OutputPdo()
                self.output_data.Mode_Operation = 1
                self.handle_pdo_process()
                
                print('dev1',self.convert_input_data(self.dev1.input).Status_word)
                print('dev2',self.convert_input_data(self.dev2.input).Status_word)
                print('dev3',self.convert_input_data(self.dev3.input).Status_word)
                print('dev4',self.convert_input_data(self.dev4.input).Status_word)
                
                for control_cmd in [6]:
                    self.output_data.Control_Word = control_cmd
                    self.dev1.output = bytes(self.output_data)
                    self.dev2.output = bytes(self.output_data)
                    self.dev3.output = bytes(self.output_data)
                    self.dev4.output = bytes(self.output_data)
                    self.handle_pdo_process()
                    
                print('dev1',self.convert_input_data(self.dev1.input).Status_word)
                print('dev2',self.convert_input_data(self.dev2.input).Status_word)
                print('dev3',self.convert_input_data(self.dev3.input).Status_word)
                print('dev4',self.convert_input_data(self.dev4.input).Status_word)
                
            else:
                print('not in op')
        else:
            print('not in safe op')
            
    def run_machine1(self):
        am.flag = 1
        print('run_machine1')
        self.dev2.sdo_write(0x6081,0x0, bytes(ctypes.c_uint32(100000)))
        self.output_data.Target_Position = 0
        for i in range(10):
            self.handle_pdo_process()
        for control_cmd in [15,31]:
            self.output_data.Control_Word = control_cmd
            self.dev2.output = bytes(self.output_data)
            self.handle_pdo_process()
            
        while self.convert_input_data(self.dev2.input).Position_actual_encoder_value not in range(-20,20) or self.convert_input_data(self.dev2.input).Status_word != 5687:
            self.handle_pdo_process()
        am.flag = 0
        
    def stop_machine1(self):
        
        pass
        
    def teardown_machine(self):
        print('teardown_machine')
        self.flag = 1
        for control_cmd in [7]:
            self.output_data.Control_Word = control_cmd
            self.dev1.output = bytes(self.output_data)
            self.dev2.output = bytes(self.output_data)
            self.dev3.output = bytes(self.output_data)
            self.dev4.output = bytes(self.output_data)
            self.handle_pdo_process()
                    
        self.dev1.output = bytes(len(self.dev1.output))
        self.dev2.output = bytes(len(self.dev2.output))
        self.dev3.output = bytes(len(self.dev3.output))
        self.dev4.output = bytes(len(self.dev4.output))
        self.handle_pdo_process()
        
        self.master.state = pysoem.PREOP_STATE
        self.master.write_state()
        
        self.master.close()
        self.flag = -1
        
    def convert_input_data(self,data):
        return InputPdo.from_buffer_copy(data)

Firstly I do not use add_hotkey

am = AM('eth0')
am.setUp()

edt = time.time()+1
while time.time() < edt:
    am.handle_pdo_process(type='out')

am.run_machine1()

am.teardown_machine()

The PDO gap is around 0.01s, and the device works well as the script. some output:

...
out 1645434037.0554166
out 1645434037.065747
out 1645434037.0760999
run_machine1
in 1645434037.088117
in 1645434037.0983844
in 1645434037.1087577
...
in 1645434041.6538827
in 1645434041.664271
in 1645434041.6746488
teardown_machine
in 1645434041.6851146
in 1645434041.6954794

I tried to use add_hotkey.

am = AM('eth0')
am.setUp()

add_hotkey('f1',am.run_machine1)
add_hotkey('f5',am.teardown_machine)
while True:
    if am.flag == 1:
        pass
    elif am.flag == -1:
        break
    else:
        am.handle_pdo_process(type='out')

The PDO gap goes to around 0.02 while I press F1, and the device doesn't work, and it still prints an 'out' after I press F1. some output:

...
out 1645433933.304413
out 1645433933.3146966
out 1645433933.3249598
^[OPrun_machine1
out 1645433933.3359287
in 1645433933.3511412
in 1645433933.371582
in 1645433933.3920348
in 1645433933.4125152
in 1645433933.4329288
...

Do you have any suggestion to let the keyboard control the devices start, stop separately?

I also tried to use subporcess.Popen to do it. the subprocess defines the total behavior of one machine (2 servos). It works well if I use one computer to control one set of machines (F1 to start it, F2 just terminates the subprocess to stop it). Now I have two sets of machines (4 servos). On one eth adapter, the PDO output is unable to jump into a queue right?

Using the keyboard to control the servo separately is my current idea. and not successful. Do you have some suggestions.

Oh, I also tried to use tkinter and pygame to design the GUI to input the params. Anyway, using one computer to start and stop one set of machines is successful now, the one set of machines I said means serval servos run in one designed behavior. Don't know how to control multiple sets of machines...

Can pysoem run on a virtual machine?

As the document said, the pysoem seems can run on linux and windows. But I only have a Mac, I tried to install ubuntu with parallel, but when I tried to use find _adapters, it can find the 'eth0', However When I tried the following code, it returns a empty list. I'm sure the servo device is correctly connected on my computer.

import pysoem
import sys

master = pysoem.Master()
master.open(sys.argv[1])
master.config_init()

print(master.slaves)

Is there any other configurations I need to do, or this library can't work on a virtual machine.
Anyway, I'm new to develop etherCAT servo. I really want to let the servo run as the code.
I also tried to use a raspberry 4b, it can find the servo as the above code. Then I tried to use sdo_write to write params,
I follows a case on the manual of the servo, I write all the params with correct indexes and types and values. I also tried to use sdo_read to check if the device got the values. The params' indexs, types and values are all good, but the servo device was still not work. I tried to read other values, like temperature, it works and reasonable.

So confused, why my device not work. Is my understanding correct? I use sdo_write gives all params that the devices need, it should work correctly?

my code like this:

import pysoem
import sys

master = pysoem.Master()
master.open(sys.argv[1])
master.config_init()

device = master.slaves[0] # I only connected one device

# 1. set device running mode, such as velocity mode
# 2. set target  and acceleration and deceleration velocity
# 3. set control words to enable the device run
# use the device.sdo_write to config the above steps

device.sdo_write(index, subindex, value) 

# optional, monitor the device state
device.sdo_read(index, subindex)
..
..
master.close()

I am grateful if anyone can point out the mistakes I made or help me solve these problems.
Thanks so much friends

Documentation and PDO/SDO Maps

Hi @bnjmnp,

Thanks for help me in #25 its work when i tested the minimal_example.py on real ETHERCAT communication, but... I still have some doubts, and i saw that in examples folder have read_sdo_info.py, it will be the next test.

My question is:

  1. How can i write on SDO's ?
  2. How to read and write PDO's ?
  3. There is some function that list all maps PDO/SDO index and address in all slaves ?
  4. There is some documentation that list all functions in pysoem and what it do ? Some functions have parameters that i dont know what is... and it will help me to build without send questions here.

ifm AL1332 EtherCAT slave - Cannot get OP state

Hello team,
I’m using the pysoem library and trying to communicate with the ifm AL1332 EtherCAT slave. I have spent ample amount of time learning and reading all the examples and issues under this GitHub repository.

I have successfully been able to do an SDO read of various indexes on the slave.

I am unable to get the device to SAFEOP or OP State, hence I am unable to do any outputs or cyclic process data exchange.

Do I HAVE to run config_func and config_map when all I am doing is using default setup? If yes, what goes in them?

Also, I’m just going to throw this out there I’m willing to pay for someone’s time to get this working with me. All I want to do is get a working cyclic exchange of data with the AL1332.

Please advice.

Can't Install on M1 mac

I get the following error when I try to install on mac.

(venv) mschem@mbp sk % pip install pysoem==1.0.4
Collecting pysoem==1.0.4
  Using cached pysoem-1.0.4.tar.gz (342 kB)
    ERROR: Command errored out with exit status 1:
     command: /Users/mschem/sk/venv/bin/python -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/48/_sj4kz4d2qn3xdqrkln2gfmc0000gn/T/pip-install-ycddbiht/pysoem_f4a291dcd77f46ccaea12e44a1752d73/setup.py'"'"'; __file__='"'"'/private/var/folders/48/_sj4kz4d2qn3xdqrkln2gfmc0000gn/T/pip-install-ycddbiht/pysoem_f4a291dcd77f46ccaea12e44a1752d73/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/48/_sj4kz4d2qn3xdqrkln2gfmc0000gn/T/pip-pip-egg-info-wbe5oi44
         cwd: /private/var/folders/48/_sj4kz4d2qn3xdqrkln2gfmc0000gn/T/pip-install-ycddbiht/pysoem_f4a291dcd77f46ccaea12e44a1752d73/
    Complete output (5 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/48/_sj4kz4d2qn3xdqrkln2gfmc0000gn/T/pip-install-ycddbiht/pysoem_f4a291dcd77f46ccaea12e44a1752d73/setup.py", line 32, in <module>
        soem_macros.append(('EC_VER2', ''))
    NameError: name 'soem_macros' is not defined
    ----------------------------------------
WARNING: Discarding https://files.pythonhosted.org/packages/b4/92/52b9c1b239b7ccc8c7032ddab1658029e3326f7b59099c2474f4d090b02c/pysoem-1.0.4.tar.gz#sha256=ae4993de7508f24467543f59a801819b64c84dcbe11df3b1de70e39415693125 (from https://pypi.org/simple/pysoem/). Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
ERROR: Could not find a version that satisfies the requirement pysoem==1.0.4 (from versions: 0.0.13, 0.0.14, 0.0.15, 0.0.16, 0.0.17, 0.0.18, 0.1.1, 1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4)
ERROR: No matching distribution found for pysoem==1.0.4
WARNING: You are using pip version 21.1.2; however, version 22.0.4 is available.
You should consider upgrading via the '/Users/mschem/sk/venv/bin/python -m pip install --upgrade pip' command.

I noticed that sys.platform returns darwin for mac, so I've added the following to the setup.py. I'm using macosx as I see that folder in soem/osal and it seems to be the right place.

elif sys.platform.startswith('darwin'):
    soem_macros = []
    soem_lib_dirs = []
    soem_libs = ['pthread', 'rt'] 
    os_name = 'macosx'

Now I get down to:

10.14-arm64-3.8/./soem/soem/ethercatmain.o build/temp.macosx-10.14-arm64-3.8/./soem/soem/ethercatprint.o build/temp.macosx-10.14-arm64-3.8/./soem/soem/ethercatsoe.o -lpthread -lrt -o build/lib.macosx-10.14-arm64-3.8/pysoem/pysoem.cpython-38-darwin.so
ld: library not found for -lrt
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: command 'clang' failed with exit status 1

I'm working on figuring out what's going on here, but I figured I'd open an issue to see if smarter minds than my own know what's going on here.

Also worth noting that I can build the c SOEM package:

build % cmake ..
-- The C compiler identification is AppleClang 13.0.0.13000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- OS is macosx
-- LIB_DIR: lib
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/mschem/SOEM/build
mschem@mbp build % make
[  4%] Building C object CMakeFiles/soem.dir/soem/ethercatbase.c.o
[  9%] Building C object CMakeFiles/soem.dir/soem/ethercatcoe.c.o
[ 14%] Building C object CMakeFiles/soem.dir/soem/ethercatconfig.c.o
[ 19%] Building C object CMakeFiles/soem.dir/soem/ethercatdc.c.o
[ 23%] Building C object CMakeFiles/soem.dir/soem/ethercateoe.c.o
[ 28%] Building C object CMakeFiles/soem.dir/soem/ethercatfoe.c.o
[ 33%] Building C object CMakeFiles/soem.dir/soem/ethercatmain.c.o
[ 38%] Building C object CMakeFiles/soem.dir/soem/ethercatprint.c.o
[ 42%] Building C object CMakeFiles/soem.dir/soem/ethercatsoe.c.o
[ 47%] Building C object CMakeFiles/soem.dir/osal/macosx/osal.c.o
[ 52%] Building C object CMakeFiles/soem.dir/oshw/macosx/nicdrv.c.o
[ 57%] Building C object CMakeFiles/soem.dir/oshw/macosx/oshw.c.o
[ 61%] Linking C static library libsoem.a
[ 61%] Built target soem
[ 66%] Building C object test/simple_ng/CMakeFiles/simple_ng.dir/simple_ng.c.o
[ 71%] Linking C executable simple_ng
[ 71%] Built target simple_ng
[ 76%] Building C object test/linux/slaveinfo/CMakeFiles/slaveinfo.dir/slaveinfo.c.o
[ 80%] Linking C executable slaveinfo
[ 80%] Built target slaveinfo
[ 85%] Building C object test/linux/eepromtool/CMakeFiles/eepromtool.dir/eepromtool.c.o
[ 90%] Linking C executable eepromtool
[ 90%] Built target eepromtool
[ 95%] Building C object test/linux/simple_test/CMakeFiles/simple_test.dir/simple_test.c.o
[100%] Linking C executable simple_test
[100%] Built target simple_test

Buffer Overflow error

Using both the basic and minimal example scripts, when I try to initialize and send data, I am getting a Buffer Overflow error-whether the cable is plugged in or not. Using Ubuntu v18, python 3.8. I am new to Python so I am wondering how should I reference the ECAT master network ID and are there any special settings I need before using the standard ethernet port as an EtherCAT port? I get a connection error if I enter anything other then the MAC ID of the adapter in the command line as a string, but that is when I get the buffer overflow error.

pdo buffer

we have 64 bit input PDO, and 64 bit output PDO.

From wireshark, I find that the data buffer in SOEM command LRW (PDO transfer) is output PDO + input PDO, i.e. 128bit.
But the data buffer of LRW in Kollmorgen EtherCAT master is output PDO, i.e. 64 bit. How do we change something to make SOEM more like the Kollmorgen EtherCAT master ?

Outputs don't stay set

Hej hej,

I managed to get my system to work with pySOEM.
Setup:

  • EK1100
  • EL3144
  • EL2624
  • EL2872 (no COE objects, so no RxPDO config)
  • EL1872 (no COE objects, so no TxPDO config)

Before starting on the analog stuff, I tried to make the digital input and outputs work.
Got as far as here:

import sys
import time
import struct
import pysoem

SDO_Info_Check = False

def read_values(ifname):

    # Create EtherCAT master instance
    master = pysoem.Master()
    
    # Open EtherCAT master instance
    master.open(ifname)
    print("EtherCAT master created and started...")

    print("Enumarating slaves")
    print('===========================================================================================')
    # Enumerate and init all slaves
    if master.config_init() > 0:

        # Read state of all slaves at start-up
        master.read_state()

        # Iterate over all slves found
        for slave in master.slaves:
            # Print info on slave
            print('{}:'.format(slave.name))

            # Read state of slave
            print('\tState: {}'.format(hex(slave.state)))

            if (SDO_Info_Check):
                # Check if SDO info is available
                try:
                    od = slave.od
                except pysoem.SdoInfoError:
                    print('\tno SDO info')
                else:
                    for obj in od:
                        print(' Idx: {}; Code: {}; Type: {}; BitSize: {}; Access: {}; Name: "{}"'.format(
                            hex(obj.index),
                            obj.object_code,
                            obj.data_type,
                            obj.bit_length,
                            hex(obj.obj_access),
                            obj.name))
                        for i, entry in enumerate(obj.entries):
                            if entry.data_type > 0 and entry.bit_length > 0:
                                print('  Subindex {}; Type: {}; BitSize: {}; Access: {} Name: "{}"'.format(
                                    i,
                                    entry.data_type,
                                    entry.bit_length,
                                    hex(entry.obj_access),
                                    entry.name))

        print('===========================================================================================')
        # Transition MASTER to SAFEOP_STATE
        # PREOP_STATE to SAFEOP_STATE request - each slave's config_func is called
        print('Transition sytem to SAFEOP_STATE')
        print('===========================================================================================')
        io_map_size = master.config_map()
        print('IOMap-Size: {}'.format(io_map_size))
        print('===========================================================================================')
        # Config DC
        print('Config DC')
        # print(master.config_dc())
        print('===========================================================================================')
        # DC Sync
        print('DC Sync')
        # master.slaves[3].dc_sync(1,1000000000)
        print('===========================================================================================')

        # wait 50 ms for all slaves to reach SAFE_OP state
        if master.state_check(pysoem.SAFEOP_STATE, 50000) != pysoem.SAFEOP_STATE:
            master.read_state()
            for slave in master.slaves:
                if not slave.state == pysoem.SAFEOP_STATE:
                    print('{} did not reach SAFEOP state'.format(slave.name))
                    print('al status code {} ({})'.format(hex(slave.al_status), pysoem.al_status_code_to_string(slave.al_status)))
            raise Exception('not all slaves reached SAFEOP state')

        # Iterate over all slves found
        for slave in master.slaves:
            # Print info on slave
            print('{}:'.format(slave.name))

            # Read state of slave
            print('\tState: {}'.format(hex(slave.state)))
        print('===========================================================================================')

        # Send and receive process data to have valid data at all outputs before transistioning to OP_STATE
        print('Send and receive process data to have valid data at all outputs before transistioning to OP_STATE')
        master.send_processdata()
        actual_wkc = master.receive_processdata(2000)
        if not actual_wkc == master.expected_wkc:
            print('incorrect wkc')

        print('===========================================================================================')
        
        # Transition MASTER to OP_STATE (Slave should follow)
        print('Transistioning system to OP_STATE')
        master.state = pysoem.OP_STATE
        master.write_state()
        print('===========================================================================================')

        master.state_check(pysoem.OP_STATE, 50000)
        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)))
            raise Exception('Not all slaves reached OP state')

        # Read state of all slaves at start-up
        master.read_state()

        # Iterate over all slves found
        for slave in master.slaves:
            # Print info on slave
            print('{}:'.format(slave.name))

            # Read state of slave
            print('\tState: {}'.format(hex(slave.state)))
        print('===========================================================================================')

        # ================= SYSTEM OPERATIONAL ==============================

        # Create individual objects
        M_00 = master.slaves[0]
        # M_01 = master.slaves[1]
        # M_02 = master.slaves[2]
        # M_03 = master.slaves[3]
        print('Waiting 3 secs...')
        time.sleep(3)

        for ii in range(50000):
            print('===========================================================================================')
            print(ii)
            
            master.slaves[3].output = struct.pack('H', 0xAAAA)
            master.send_processdata()
            actual_wkc = master.receive_processdata(2000)
            if not actual_wkc == master.expected_wkc:
                print('incorrect wkc')
            print(master.slaves[3].output.hex())
            print(master.slaves[4].input.hex())

            master.read_state()
            # Iterate over all slves found
            for slave in master.slaves:
                # Print info on slave
                print('{}:'.format(slave.name))

                # Read state of slave
                print('\tState: {}'.format(hex(slave.state)))

            time.sleep(1)
            
            master.slaves[3].output = struct.pack('H', 0x5555)
            master.send_processdata()
            master.receive_processdata(2000)
            if not actual_wkc == master.expected_wkc:
                print('incorrect wkc')
            print(master.slaves[3].output.hex())
            print(master.slaves[4].input.hex())
            
            time.sleep(1)


        #for _ in range(5):
        #    # Write to 1010 1010 1010 1010
        #    master.slaves[5].output = struct.pack('H', 0xAAAA)
        #    time.sleep(0.5)
        #    print(master.slaves[6].input)
        #    time.sleep(3)
            
        #    # Write to 0101 0101 0101 0101‬
        #    master.slaves[5].output = struct.pack('H', 0x5555)
        #    time.sleep(0.5)
        #    print(master.slaves[6].input)
        #    time.sleep(3)


        # Write Analog Output Ch.1 of EL4008 (position 1) to 5V
        # M_01.sdo_write(0x7000, 1, struct.pack('H', 2048))

        time.sleep(1)

        print('===========================================================================================')
        # ================= SHUTDOWN =======================================
        # Transition MASTER to INIT (Slave should follow)
        print('Transistioning system to INIT_STATE')
        master.state = pysoem.INIT_STATE
        master.write_state()

        # Read state of all slaves
        time.sleep(3)

        master.read_state()
        # Iterate over all slves found
        for slave in master.slaves:
            # Print info on slave
            print('{}:'.format(slave.name))

            # Read state of slave
            print('\tState: {}'.format(hex(slave.state)))
        print('===========================================================================================')

    # IF NO SLAVES AVAILABLE !!
    else:
        print('no slave available')

    master.close()

if __name__ == '__main__':

    print('script started')

    if len(sys.argv) > 1:
        read_values(sys.argv[1])
    else:
        print('give ifname as script argument')

Problem:
When I set the outputs, they are set as I want them to be, but only for a split second (LEDs flash).
I get the feeling, this is related to DC stuff and maybe me not sending / receiving regularly...
Could you maybe comment if I'm correct?
If that's the case, I will try to go deeper in those topics...

Is sending / receiving regularly required?
The purse of the system right now is mainly providing software-timed on demand input readings and setting outputs to a specific value, until changed again...

Cheers
Niels

P.S.: I'm getting started with EtherCAT in python, and it seems there is alittel bit of context still missing in my understanding of it..

License question

Dear @bnjmnp,

thank you very much for this great wrapper! I would like to know why you chose a GPL-2.0 licence, which allows commercial use, but only under the condition that the source code must be published.

Therefore I have two questions:

  1. would you consider putting the project under a different licence (e.g. MIT)?
  2. if you do not want to do this, would you license the project, i.e. give a company the right to use the code without having to publish its own source code?

Many greetings and many thanks

Slave Setup

Hi,

I am new to EtherCat. I got a MXU4300 from Infineon and would like to set up a simple slave master communication. My problem is that I do not understand how to implement the slave in a setup function.

I followed the instruction for the XMC4300 board here.

The mapping for receive and transmit message can be found here.

Screenshot from 2019-11-02 21-21-06

I had a look at the original SOEM as well as the PYSOEM examples (but could not find enough information). Any help on how to setup the setup funtion would be very helpful and mybe a good example for this repo because I think that the XMC4300 is a nice and cheap board.

Many thanks

how to send NOP command frame

I'm testing the Kollmorgen AKD2G drive, but the pysoem can't work correctly with it. I captured the frame from PCMM (Kollmorgen EtherCAT master) to AKD2G drive, and I find that the PCMM sends NOP command periodically to keep something.

Can the pysoem send the NOP command? How to enable this?

A question of ESI and PDO


labels: Need Help

Hi @bnjmnp

One thing I'm confused is how the fields mapped to the params address, it even not use any hex address. Such as 0x6040 or anything else. Actually, I'd like to know How i config the the 'inputPDO' and 'outputPDO' according to my communication object dictionary. (My device is PMM6040, with ethercat by CIA402).

My code:

import pysoem
import time
import ctypes
import sys

tmcm1617 = None


class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('modes_of_operation_display', ctypes.c_int8),
        ('statusword', ctypes.c_uint16),
        ('position_demand_value', ctypes.c_int32),
        ('position_actual_value', ctypes.c_int32),
        ('velocity_demand_value', ctypes.c_int32),
        ('velocity_actual_value', ctypes.c_int32),
        ('torque_demand_value', ctypes.c_int32),
        ('torque_actual_value', ctypes.c_int32),
        ('digital_input', ctypes.c_uint32),
    ]


class OutputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('modes_of_operation', ctypes.c_int8),
        ('controlword', ctypes.c_uint16),
        ('target_position', ctypes.c_int32),
        ('target_velocity', ctypes.c_int32),
        ('target_torque', ctypes.c_int32),
        ('digital_output', ctypes.c_uint32),
    ]


modes_of_operation = {
    'No mode': 0,
    'Profile position mode': 1,
    'Velocity mode':2,
    '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 tmcm1617_config_func(slave_pos):
    global tmcm1617
    # All default config


def main():
    global tmcm1617
    master = pysoem.Master()
    master.open(sys.argv[1])  
    if master.config_init() > 0:
        tmcm1617 = master.slaves[0]
        tmcm1617.config_func = tmcm1617_config_func
        master.config_map()
        if master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE:
            master.state = pysoem.OP_STATE
            
            master.send_processdata()
            master.receive_processdata(1_000)
            
            master.write_state()
            master.state_check(pysoem.OP_STATE, 5_000_000)
            if master.state == pysoem.OP_STATE:
                print('IN OP STATE')
                output_data = OutputPdo()
                output_data.modes_of_operation = modes_of_operation['Profile velocity mode']
                output_data.target_velocity = 500 
                for control_cmd in [6, 7, 15]:
                    output_data.controlword = control_cmd
                    tmcm1617.output = bytes(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)
                        #  when I tried to print input values here, got error like: buffer too small, 28 not 31
                        time.sleep(0.01)
                except KeyboardInterrupt:
                    print('stopped')
                # zero everything
                tmcm1617.output = bytes(len(tmcm1617.output))
                master.send_processdata()
                master.receive_processdata(1_000)
            else:
                print('al status code {} ({})'.format(hex(tmcm1617.al_status), pysoem.al_status_code_to_string(tmcm1617.al_status)))
                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()

The light is blink, seems not in OP state.
I don't have a ESI file, I have a communication book, there are too many params, not sure what I need to config in 'InputPDO' and 'outputPDO'. And is there any limits of the names in the fields of InputPDO' and 'outputPDO'.

Thanks so much!
Best Regards
Tom

Slave Setup

Hi,

I am new to EtherCat
I am using Stepping motor Driver AZD2A-KED here
My problem is that I do not understand how to implement the slave in a setup function.

Could you help me?.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.