@@ -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 )
0 commit comments