@@ -87,9 +87,11 @@ class State402(object):
87
87
88
88
@staticmethod
89
89
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
93
95
"""
94
96
for cond , next_state in State402 .NEXTSTATE2ENABLE .items ():
95
97
if _from in cond :
@@ -209,10 +211,10 @@ def __init__(self, node_id, object_dictionary):
209
211
self .rpdo_pointers = dict () # { index: RPDO_pointer }
210
212
211
213
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.
216
218
"""
217
219
self .nmt .state = 'PRE-OPERATIONAL' # Why is this necessary?
218
220
self .setup_pdos ()
@@ -258,8 +260,7 @@ def _check_statusword_configured(self):
258
260
self .id ))
259
261
260
262
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."""
263
264
if self .state == 'FAULT' :
264
265
# Resets the Fault Reset bit (rising edge 0 -> 1)
265
266
self .controlword = State402 .CW_DISABLE_VOLTAGE
@@ -275,7 +276,12 @@ def is_faulted(self):
275
276
return self .statusword & bitmask == bits
276
277
277
278
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
+ """
279
285
previous_op_mode = self .op_mode
280
286
if previous_op_mode != 'HOMING' :
281
287
logger .info ('Switch to HOMING from %s' , previous_op_mode )
@@ -290,11 +296,13 @@ def is_homed(self, restore_op_mode=False):
290
296
return homingstatus in ('TARGET REACHED' , 'ATTAINED' )
291
297
292
298
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.
298
306
:rtype: bool
299
307
"""
300
308
previus_op_mode = self .op_mode
@@ -333,20 +341,11 @@ def homing(self, timeout=TIMEOUT_HOMING_DEFAULT, set_new_home=True):
333
341
334
342
@property
335
343
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.
341
345
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:
348
348
349
- The modes can be:
350
349
- 'NO MODE'
351
350
- 'PROFILED POSITION'
352
351
- 'VELOCITY'
@@ -359,7 +358,14 @@ def op_mode(self, mode):
359
358
- 'CYCLIC SYNCHRONOUS TORQUE'
360
359
- 'OPEN LOOP SCALAR MODE'
361
360
- '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.
362
364
"""
365
+ return OperationMode .CODE2NAME [self .sdo [0x6061 ].raw ]
366
+
367
+ @op_mode .setter
368
+ def op_mode (self , mode ):
363
369
try :
364
370
if not self .is_op_mode_supported (mode ):
365
371
raise TypeError (
@@ -381,15 +387,13 @@ def op_mode(self, mode):
381
387
raise RuntimeError (
382
388
"Timeout setting node {0}'s new mode of operation to {1}." .format (
383
389
self .id , mode ))
384
- return True
385
390
except SdoCommunicationError as e :
386
391
logger .warning ('[SDO communication error] Cause: {0}' .format (str (e )))
387
392
except (RuntimeError , ValueError ) as e :
388
393
logger .warning ('{0}' .format (str (e )))
389
394
finally :
390
395
self .state = start_state # why?
391
396
logger .info ('Set node {n} operation mode to {m}.' .format (n = self .id , m = mode ))
392
- return False
393
397
394
398
def _clear_target_values (self ):
395
399
# [target velocity, target position, target torque]
@@ -398,9 +402,13 @@ def _clear_target_values(self):
398
402
self .sdo [target_index ].raw = 0
399
403
400
404
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.
404
412
:rtype: bool
405
413
"""
406
414
if not hasattr (self , '_op_mode_support' ):
@@ -412,18 +420,20 @@ def is_op_mode_supported(self, mode):
412
420
return self ._op_mode_support & bits == bits
413
421
414
422
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
418
427
"""
419
428
for obj in mapobject :
420
429
self .tpdo_values [obj .index ] = obj .raw
421
430
422
431
@property
423
432
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.
427
437
"""
428
438
try :
429
439
return self .tpdo_values [0x6041 ]
@@ -433,13 +443,15 @@ def statusword(self):
433
443
434
444
@property
435
445
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
+ """
436
451
raise RuntimeError ('The Controlword is write-only.' )
437
452
438
453
@controlword .setter
439
454
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
- """
443
455
if 0x6040 in self .rpdo_pointers :
444
456
self .rpdo_pointers [0x6040 ].raw = value
445
457
self .rpdo_pointers [0x6040 ].pdo_parent .transmit ()
@@ -448,16 +460,24 @@ def controlword(self, value):
448
460
449
461
@property
450
462
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)
454
470
- 'SWITCH ON DISABLED'
455
471
- 'READY TO SWITCH ON'
456
472
- 'SWITCHED ON'
457
473
- 'OPERATION ENABLED'
458
- - 'FAULT'
459
- - 'FAULT REACTION ACTIVE'
474
+ - 'FAULT' (cannot be switched to deliberately)
475
+ - 'FAULT REACTION ACTIVE' (cannot be switched to deliberately)
460
476
- '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.
461
481
"""
462
482
for state , mask_val_pair in State402 .SW_MASK .items ():
463
483
bitmask , bits = mask_val_pair
@@ -467,18 +487,6 @@ def state(self):
467
487
468
488
@state .setter
469
489
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
- """
482
490
timeout = time .monotonic () + self .TIMEOUT_SWITCH_STATE_FINAL
483
491
while self .state != target_state :
484
492
next_state = self ._next_state (target_state )
0 commit comments