pysoem
pysoem copied to clipboard
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
Hi @marcAC,
good to see that you could find a solution to your initial question.
What sdo_read read returns are the raw bytes of that object. Bytes you will also get for example, if you read from a binary file (doing something like with open('file.bin', 'rb') as f:
+ f.read()
).
If there is an integer behind thees bytes, you could do:
print(int.from_bytes(result, byteorder='little'))
If it's a signed integer you must add signed=True
, see here https://docs.python.org/3/library/stdtypes.html#int.from_bytes for details.
You could also use the struct
module. But thats a bit more difficult to explain.
If your next step is writing objects, take a look at issue #16 there I elaborate on this a bit. You need the revers of int.from_bytes()
there, witch is int.to_bytes()
.
Hey @bnjmnp,
thanks for your reply. Then maybe there is some weird problem with our slave, because the result which should be 7, is b'\x00\x00\xe0@' and 1088421888 after converting it.
EDIT: I was trying to convert "float"-bytes to int, which obviously wasn't going to work, so I was able to manage to handle it with struct.unpack. Thanks for your help!
Hey @bnjmnp,
writing SDOs is working, are there any examples on how to write PDOs?
Kind regards Marc
Hi @marcAC,
good to see that you had success. Interesting that your device uses float type parameter.
For PDO, take a look at the _pdo_update_loop function in the basic_example.py. You need to know how the PDO is composed and what is behind those bytes. In the example there is just one bit toggled periodically, which toggles a digital output. The manual of your device should help you here. You need to dig in there a bit. It is not that easy to understand.
Hey @bnjmnp,
I think it's because it's not really a standard device.
I already looked at this example but our problem is that we also mapped many values into one byte because there are also boolean-values. The C-structs we used for this looked like the following so as you can see there are 9 parameters in 2 bytes.
typedef struct PACKED {
BOOLEAN(PulsingEnabled); /* Subindex1 - Pulsing Enabled */
BOOLEAN(RampingInProgress); /* Subindex2 - Ramping in progress */
BOOLEAN(ArcDetectionEnabled); /* Subindex3 - Arc Detection Enabled */
BOOLEAN(ArcReactionEnabled); /* Subindex4 - Arc Reaction Enabled */
BOOLEAN(CEXEnabled); /* Subindex5 - CEX Enabled */
BOOLEAN(CEXLocked); /* Subindex6 - CEX Locked */
BOOLEAN(FrequencyTuned); /* Subindex7 - Frequency Tuned */
BOOLEAN(JouleModeEnergyLimitReached); /* Subindex8 - Joule Mode: Energy limit reached */
BOOLEAN(ProcessTimerEndOfProcessTime); /* Subindex9 - Process Timer: End of process time */
ALIGN7(SI10); /* Subindex10 */
} TOBJ6001;
Do you think there is a possibility to split up the bytearray more to map a struct-like structure like the one above to it?
Thanks for your help!
EDIT:
In case it is interesting for someone. We found a way to do it with ctypes Structures like this:
class TOBJ7004(ctypes.LittleEndianStructure):
_fields_ = [
("RegulationControlMode", c_uint8, 8),
("SI2", c_uint8, 8),
]
class TOBJ7005(ctypes.LittleEndianStructure):
_fields_ = [
("Setpoint", c_float)
]
class TOBJOUT(ctypes.LittleEndianStructure):
_fields_ = [
("tobj7000", TOBJ7000),
("tobj7001", TOBJ7001),
("tobj7004", TOBJ7004),
("tobj7005", TOBJ7005)
]
The only trouble we have is with the c_float.
Oh yes, you can also convert bytes to ctypes using ctypes.
Then you do something like:
tobj7004 = TOBJ7004(8, 8)
foo = bytes(tobj7004)
... to get the bytes of that struct instance.
Or:
tobj7004 = TOBJ7004.from_buffer_copy(bytes([0x55, 0xAA]))
... to convert a byte array to a struct.
I'm pretty sure you have a problem with alignment.
Add a _pack_ = 1
to your struct definition like this:
class TOBJOUT(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
("tobj7004", TOBJ7004),
("tobj7005", TOBJ7005)
]
Then the c_float will be correct non-aligned.
Awesome, that did the trick, thank you!
Is my guess correct that it will work the same for PDO read?
EDIT: Unfortunately it doesn't. In the minimal example you read the PDOs and convert the whole bytearray to int16 but in our case we have many different datatypes in the struct. The solution I found for this is to use ctypes.from_buffer_copy but if there is any more elegant solution you know feel free to share.