Skip to content

Commit 6ba36dc

Browse files
Merge pull request #256 from acolomb/ds402-documentation
2 parents adc15da + 4e4bc90 commit 6ba36dc

File tree

2 files changed

+73
-58
lines changed

2 files changed

+73
-58
lines changed

canopen/profiles/p402.py

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ class State402(object):
8787

8888
@staticmethod
8989
def next_state_for_enabling(_from):
90-
"""Returns the next state needed for reach the state Operation Enabled
91-
:param string target: Target state
92-
:return string: Next target to chagne
90+
"""Return the next state needed for reach the state Operation Enabled.
91+
92+
:param str target: Target state.
93+
:return: Next target to change.
94+
:rtype: str
9395
"""
9496
for cond, next_state in State402.NEXTSTATE2ENABLE.items():
9597
if _from in cond:
@@ -209,10 +211,10 @@ def __init__(self, node_id, object_dictionary):
209211
self.rpdo_pointers = dict() # { index: RPDO_pointer }
210212

211213
def setup_402_state_machine(self):
212-
"""Configure the state machine by searching for a TPDO that has the
213-
StatusWord mapped.
214-
:raise ValueError: If the the node can't find a Statusword configured
215-
in the any of the TPDOs
214+
"""Configure the state machine by searching for a TPDO that has the StatusWord mapped.
215+
216+
:raises ValueError:
217+
If the the node can't find a Statusword configured in any of the TPDOs.
216218
"""
217219
self.nmt.state = 'PRE-OPERATIONAL' # Why is this necessary?
218220
self.setup_pdos()
@@ -258,8 +260,7 @@ def _check_statusword_configured(self):
258260
self.id))
259261

260262
def reset_from_fault(self):
261-
"""Reset node from fault and set it to Operation Enable state
262-
"""
263+
"""Reset node from fault and set it to Operation Enable state."""
263264
if self.state == 'FAULT':
264265
# Resets the Fault Reset bit (rising edge 0 -> 1)
265266
self.controlword = State402.CW_DISABLE_VOLTAGE
@@ -275,7 +276,12 @@ def is_faulted(self):
275276
return self.statusword & bitmask == bits
276277

277278
def is_homed(self, restore_op_mode=False):
278-
"""Switch to homing mode and determine its status."""
279+
"""Switch to homing mode and determine its status.
280+
281+
:param bool restore_op_mode: Switch back to the previous operation mode when done.
282+
:return: If the status indicates successful homing.
283+
:rtype: bool
284+
"""
279285
previous_op_mode = self.op_mode
280286
if previous_op_mode != 'HOMING':
281287
logger.info('Switch to HOMING from %s', previous_op_mode)
@@ -290,11 +296,13 @@ def is_homed(self, restore_op_mode=False):
290296
return homingstatus in ('TARGET REACHED', 'ATTAINED')
291297

292298
def homing(self, timeout=TIMEOUT_HOMING_DEFAULT, set_new_home=True):
293-
"""Function to execute the configured Homing Method on the node
294-
:param int timeout: Timeout value (default: 30)
295-
:param bool set_new_home: Defines if the node should set the home offset
296-
object (0x607C) to the current position after the homing procedure (default: true)
297-
:return: If the homing was complete with success
299+
"""Execute the configured Homing method on the node.
300+
301+
:param int timeout: Timeout value (default: 30).
302+
:param bool set_new_home:
303+
Defines if the node should set the home offset object (0x607C) to the current
304+
position after the homing procedure (default: true).
305+
:return: If the homing was complete with success.
298306
:rtype: bool
299307
"""
300308
previus_op_mode = self.op_mode
@@ -333,20 +341,11 @@ def homing(self, timeout=TIMEOUT_HOMING_DEFAULT, set_new_home=True):
333341

334342
@property
335343
def op_mode(self):
336-
"""
337-
:return: Return the operation mode stored in the object 0x6061 through SDO
338-
:rtype: int
339-
"""
340-
return OperationMode.CODE2NAME[self.sdo[0x6061].raw]
344+
"""The node's Operation Mode stored in the object 0x6061.
341345
342-
@op_mode.setter
343-
def op_mode(self, mode):
344-
"""Function to define the operation mode of the node
345-
:param string mode: Mode to define.
346-
:return: Return if the operation mode was set with success or not
347-
:rtype: bool
346+
Uses SDO to access the current value. The modes are passed as one of the
347+
following strings:
348348
349-
The modes can be:
350349
- 'NO MODE'
351350
- 'PROFILED POSITION'
352351
- 'VELOCITY'
@@ -359,7 +358,14 @@ def op_mode(self, mode):
359358
- 'CYCLIC SYNCHRONOUS TORQUE'
360359
- 'OPEN LOOP SCALAR MODE'
361360
- 'OPEN LOOP VECTOR MODE'
361+
362+
:raises TypeError: When setting a mode not advertised as supported by the node.
363+
:raises RuntimeError: If the switch is not confirmed within the configured timeout.
362364
"""
365+
return OperationMode.CODE2NAME[self.sdo[0x6061].raw]
366+
367+
@op_mode.setter
368+
def op_mode(self, mode):
363369
try:
364370
if not self.is_op_mode_supported(mode):
365371
raise TypeError(
@@ -381,15 +387,13 @@ def op_mode(self, mode):
381387
raise RuntimeError(
382388
"Timeout setting node {0}'s new mode of operation to {1}.".format(
383389
self.id, mode))
384-
return True
385390
except SdoCommunicationError as e:
386391
logger.warning('[SDO communication error] Cause: {0}'.format(str(e)))
387392
except (RuntimeError, ValueError) as e:
388393
logger.warning('{0}'.format(str(e)))
389394
finally:
390395
self.state = start_state # why?
391396
logger.info('Set node {n} operation mode to {m}.'.format(n=self.id, m=mode))
392-
return False
393397

394398
def _clear_target_values(self):
395399
# [target velocity, target position, target torque]
@@ -398,9 +402,13 @@ def _clear_target_values(self):
398402
self.sdo[target_index].raw = 0
399403

400404
def is_op_mode_supported(self, mode):
401-
"""Function to check if the operation mode is supported by the node
402-
:param int mode: Operation mode
403-
:return: If the operation mode is supported
405+
"""Check if the operation mode is supported by the node.
406+
407+
The object listing the supported modes is retrieved once using SDO, then cached
408+
for later checks.
409+
410+
:param str mode: Same format as the :attr:`op_mode` property.
411+
:return: If the operation mode is supported.
404412
:rtype: bool
405413
"""
406414
if not hasattr(self, '_op_mode_support'):
@@ -412,18 +420,20 @@ def is_op_mode_supported(self, mode):
412420
return self._op_mode_support & bits == bits
413421

414422
def on_TPDOs_update_callback(self, mapobject):
415-
"""This function receives a map object.
416-
this map object is then used for changing the
417-
:param mapobject: :class: `canopen.objectdictionary.Variable`
423+
"""Cache updated values from a TPDO received from this node.
424+
425+
:param mapobject: The received PDO message.
426+
:type mapobject: canopen.pdo.Map
418427
"""
419428
for obj in mapobject:
420429
self.tpdo_values[obj.index] = obj.raw
421430

422431
@property
423432
def statusword(self):
424-
"""Returns the last read value of the Statusword (0x6041) from the device.
425-
If the the object 0x6041 is not configured in any TPDO it will fallback to the SDO mechanism
426-
and try to tget the value.
433+
"""Return the last read value of the Statusword (0x6041) from the device.
434+
435+
If the object 0x6041 is not configured in any TPDO it will fall back to the SDO
436+
mechanism and try to get the value.
427437
"""
428438
try:
429439
return self.tpdo_values[0x6041]
@@ -433,13 +443,15 @@ def statusword(self):
433443

434444
@property
435445
def controlword(self):
446+
"""Send a state change command using PDO or SDO.
447+
448+
:param int value: Controlword value to set.
449+
:raises RuntimeError: Read access to the controlword is not intended.
450+
"""
436451
raise RuntimeError('The Controlword is write-only.')
437452

438453
@controlword.setter
439454
def controlword(self, value):
440-
"""Send the state using PDO or SDO objects.
441-
:param int value: State value to send in the message
442-
"""
443455
if 0x6040 in self.rpdo_pointers:
444456
self.rpdo_pointers[0x6040].raw = value
445457
self.rpdo_pointers[0x6040].pdo_parent.transmit()
@@ -448,16 +460,24 @@ def controlword(self, value):
448460

449461
@property
450462
def state(self):
451-
"""Attribute to get or set node's state as a string for the DS402 State Machine.
452-
States of the node can be one of:
453-
- 'NOT READY TO SWITCH ON'
463+
"""Manipulate current state of the DS402 State Machine on the node.
464+
465+
Uses the last received Statusword value for read access, and manipulates the
466+
:attr:`controlword` for changing states. The states are passed as one of the
467+
following strings:
468+
469+
- 'NOT READY TO SWITCH ON' (cannot be switched to deliberately)
454470
- 'SWITCH ON DISABLED'
455471
- 'READY TO SWITCH ON'
456472
- 'SWITCHED ON'
457473
- 'OPERATION ENABLED'
458-
- 'FAULT'
459-
- 'FAULT REACTION ACTIVE'
474+
- 'FAULT' (cannot be switched to deliberately)
475+
- 'FAULT REACTION ACTIVE' (cannot be switched to deliberately)
460476
- 'QUICK STOP ACTIVE'
477+
- 'DISABLE VOLTAGE' (only as a command when writing)
478+
479+
:raises RuntimeError: If the switch is not confirmed within the configured timeout.
480+
:raises ValueError: Trying to execute a illegal transition in the state machine.
461481
"""
462482
for state, mask_val_pair in State402.SW_MASK.items():
463483
bitmask, bits = mask_val_pair
@@ -467,18 +487,6 @@ def state(self):
467487

468488
@state.setter
469489
def state(self, target_state):
470-
""" Defines the state for the DS402 state machine
471-
States to switch to can be one of:
472-
- 'SWITCH ON DISABLED'
473-
- 'DISABLE VOLTAGE'
474-
- 'READY TO SWITCH ON'
475-
- 'SWITCHED ON'
476-
- 'OPERATION ENABLED'
477-
- 'QUICK STOP ACTIVE'
478-
:param string target_state: Target state
479-
:raise RuntimeError: Occurs when the time defined to change the state is reached
480-
:raise ValueError: Occurs when trying to execute a ilegal transition in the sate machine
481-
"""
482490
timeout = time.monotonic() + self.TIMEOUT_SWITCH_STATE_FINAL
483491
while self.state != target_state:
484492
next_state = self._next_state(target_state)

doc/profiles.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,10 @@ Available commands
8585
- 'SWITCHED ON'
8686
- 'OPERATION ENABLED'
8787
- 'QUICK STOP ACTIVE'
88+
89+
90+
API
91+
```
92+
93+
.. autoclass:: canopen.profiles.p402.BaseNode402
94+
:members:

0 commit comments

Comments
 (0)