6
6
7
7
logger = logging .getLogger (__name__ )
8
8
9
+
9
10
class State402 (object ):
10
11
# Controlword (0x6040) commands
11
- CW_OPERATION_ENABLED = 0x0F
12
- CW_SHUTDOWN = 0x06
13
- CW_SWITCH_ON = 0x07
14
- CW_QUICK_STOP = 0x02
15
- CW_DISABLE_VOLTAGE = 0x00
16
- CW_SWITCH_ON_DISABLED = 0x80
12
+ CW_OPERATION_ENABLED = 0x000F
13
+ CW_SHUTDOWN = 0x0006
14
+ CW_SWITCH_ON = 0x0007
15
+ CW_QUICK_STOP = 0x0002
16
+ CW_DISABLE_VOLTAGE = 0x0000
17
+ CW_SWITCH_ON_DISABLED = 0x0080
17
18
18
19
CW_CODE_COMMANDS = {
19
- CW_SWITCH_ON_DISABLED : 'SWITCH ON DISABLED' ,
20
- CW_DISABLE_VOLTAGE : 'DISABLE VOLTAGE' ,
21
- CW_SHUTDOWN : 'READY TO SWITCH ON' ,
22
- CW_SWITCH_ON : 'SWITCHED ON' ,
23
- CW_OPERATION_ENABLED : 'OPERATION ENABLED' ,
24
- CW_QUICK_STOP : 'QUICK STOP ACTIVE'
20
+ CW_SWITCH_ON_DISABLED : 'SWITCH ON DISABLED' ,
21
+ CW_DISABLE_VOLTAGE : 'DISABLE VOLTAGE' ,
22
+ CW_SHUTDOWN : 'READY TO SWITCH ON' ,
23
+ CW_SWITCH_ON : 'SWITCHED ON' ,
24
+ CW_OPERATION_ENABLED : 'OPERATION ENABLED' ,
25
+ CW_QUICK_STOP : 'QUICK STOP ACTIVE' ,
25
26
}
26
27
27
28
CW_COMMANDS_CODE = {
28
- 'SWITCH ON DISABLED' : CW_SWITCH_ON_DISABLED ,
29
- 'DISABLE VOLTAGE' : CW_DISABLE_VOLTAGE ,
30
- 'READY TO SWITCH ON' : CW_SHUTDOWN ,
31
- 'SWITCHED ON' : CW_SWITCH_ON ,
32
- 'OPERATION ENABLED' : CW_OPERATION_ENABLED ,
33
- 'QUICK STOP ACTIVE' : CW_QUICK_STOP
29
+ 'SWITCH ON DISABLED' : CW_SWITCH_ON_DISABLED ,
30
+ 'DISABLE VOLTAGE' : CW_DISABLE_VOLTAGE ,
31
+ 'READY TO SWITCH ON' : CW_SHUTDOWN ,
32
+ 'SWITCHED ON' : CW_SWITCH_ON ,
33
+ 'OPERATION ENABLED' : CW_OPERATION_ENABLED ,
34
+ 'QUICK STOP ACTIVE' : CW_QUICK_STOP ,
34
35
}
35
36
36
37
# Statusword 0x6041 bitmask and values in the list in the dictionary value
37
38
SW_MASK = {
38
- 'NOT READY TO SWITCH ON' : [ 0x4F , 0x00 ] ,
39
- 'SWITCH ON DISABLED' : [ 0x4F , 0x40 ] ,
40
- 'READY TO SWITCH ON' : [ 0x6F , 0x21 ] ,
41
- 'SWITCHED ON' : [ 0x6F , 0x23 ] ,
42
- 'OPERATION ENABLED' : [ 0x6F , 0x27 ] ,
43
- 'FAULT' : [ 0x4F , 0x08 ] ,
44
- 'FAULT REACTION ACTIVE' : [ 0x4F , 0x0F ] ,
45
- 'QUICK STOP ACTIVE' : [ 0x6F , 0x07 ]
39
+ 'NOT READY TO SWITCH ON' : ( 0x4F , 0x00 ) ,
40
+ 'SWITCH ON DISABLED' : ( 0x4F , 0x40 ) ,
41
+ 'READY TO SWITCH ON' : ( 0x6F , 0x21 ) ,
42
+ 'SWITCHED ON' : ( 0x6F , 0x23 ) ,
43
+ 'OPERATION ENABLED' : ( 0x6F , 0x27 ) ,
44
+ 'FAULT' : ( 0x4F , 0x08 ) ,
45
+ 'FAULT REACTION ACTIVE' : ( 0x4F , 0x0F ) ,
46
+ 'QUICK STOP ACTIVE' : ( 0x6F , 0x07 ),
46
47
}
47
48
48
49
# Transition path to get to the 'OPERATION ENABLED' state
49
50
NEXTSTATE2ENABLE = {
50
- ('START' ) : 'NOT READY TO SWITCH ON' ,
51
- ('FAULT' , 'NOT READY TO SWITCH ON' ) : 'SWITCH ON DISABLED' ,
52
- ('SWITCH ON DISABLED' ) : 'READY TO SWITCH ON' ,
53
- ('READY TO SWITCH ON' ) : 'SWITCHED ON' ,
54
- ('SWITCHED ON' , 'QUICK STOP ACTIVE' , 'OPERATION ENABLED' ) : 'OPERATION ENABLED' ,
55
- ('FAULT REACTION ACTIVE' ) : 'FAULT'
51
+ ('START' ): 'NOT READY TO SWITCH ON' ,
52
+ ('FAULT' , 'NOT READY TO SWITCH ON' ): 'SWITCH ON DISABLED' ,
53
+ ('SWITCH ON DISABLED' ): 'READY TO SWITCH ON' ,
54
+ ('READY TO SWITCH ON' ): 'SWITCHED ON' ,
55
+ ('SWITCHED ON' , 'QUICK STOP ACTIVE' , 'OPERATION ENABLED' ): 'OPERATION ENABLED' ,
56
+ ('FAULT REACTION ACTIVE' ): 'FAULT'
56
57
}
57
58
58
59
# Tansition table from the DS402 State Machine
@@ -110,35 +111,45 @@ class OperationMode(object):
110
111
OPEN_LOOP_VECTOR_MODE = - 2
111
112
112
113
CODE2NAME = {
113
- NO_MODE : 'NO MODE' ,
114
- PROFILED_POSITION : 'PROFILED POSITION' ,
115
- VELOCITY : 'VELOCITY' ,
116
- PROFILED_VELOCITY : 'PROFILED VELOCITY' ,
117
- PROFILED_TORQUE : 'PROFILED TORQUE' ,
118
- HOMING : 'HOMING' ,
119
- INTERPOLATED_POSITION : 'INTERPOLATED POSITION'
114
+ NO_MODE : 'NO MODE' ,
115
+ PROFILED_POSITION : 'PROFILED POSITION' ,
116
+ VELOCITY : 'VELOCITY' ,
117
+ PROFILED_VELOCITY : 'PROFILED VELOCITY' ,
118
+ PROFILED_TORQUE : 'PROFILED TORQUE' ,
119
+ HOMING : 'HOMING' ,
120
+ INTERPOLATED_POSITION : 'INTERPOLATED POSITION' ,
121
+ CYCLIC_SYNCHRONOUS_POSITION : 'CYCLIC SYNCHRONOUS POSITION' ,
122
+ CYCLIC_SYNCHRONOUS_VELOCITY : 'CYCLIC SYNCHRONOUS VELOCITY' ,
123
+ CYCLIC_SYNCHRONOUS_TORQUE : 'CYCLIC SYNCHRONOUS TORQUE' ,
120
124
}
121
125
122
126
NAME2CODE = {
123
- 'NO MODE' : NO_MODE ,
124
- 'PROFILED POSITION' : PROFILED_POSITION ,
125
- 'VELOCITY' : VELOCITY ,
126
- 'PROFILED VELOCITY' : PROFILED_VELOCITY ,
127
- 'PROFILED TORQUE' : PROFILED_TORQUE ,
128
- 'HOMING' : HOMING ,
129
- 'INTERPOLATED POSITION' : INTERPOLATED_POSITION
127
+ 'NO MODE' : NO_MODE ,
128
+ 'PROFILED POSITION' : PROFILED_POSITION ,
129
+ 'VELOCITY' : VELOCITY ,
130
+ 'PROFILED VELOCITY' : PROFILED_VELOCITY ,
131
+ 'PROFILED TORQUE' : PROFILED_TORQUE ,
132
+ 'HOMING' : HOMING ,
133
+ 'INTERPOLATED POSITION' : INTERPOLATED_POSITION ,
134
+ 'CYCLIC SYNCHRONOUS POSITION' : CYCLIC_SYNCHRONOUS_POSITION ,
135
+ 'CYCLIC SYNCHRONOUS VELOCITY' : CYCLIC_SYNCHRONOUS_VELOCITY ,
136
+ 'CYCLIC SYNCHRONOUS TORQUE' : CYCLIC_SYNCHRONOUS_TORQUE ,
130
137
}
131
138
132
139
SUPPORTED = {
133
- 'NO MODE' : 0x0 ,
134
- 'PROFILED POSITION' : 0x1 ,
135
- 'VELOCITY' : 0x2 ,
136
- 'PROFILED VELOCITY' : 0x4 ,
137
- 'PROFILED TORQUE' : 0x8 ,
138
- 'HOMING' : 0x20 ,
139
- 'INTERPOLATED POSITION' : 0x40
140
+ 'NO MODE' : 0x0000 ,
141
+ 'PROFILED POSITION' : 0x0001 ,
142
+ 'VELOCITY' : 0x0002 ,
143
+ 'PROFILED VELOCITY' : 0x0004 ,
144
+ 'PROFILED TORQUE' : 0x0008 ,
145
+ 'HOMING' : 0x0020 ,
146
+ 'INTERPOLATED POSITION' : 0x0040 ,
147
+ 'CYCLIC SYNCHRONOUS POSITION' : 0x0080 ,
148
+ 'CYCLIC SYNCHRONOUS VELOCITY' : 0x0100 ,
149
+ 'CYCLIC SYNCHRONOUS TORQUE' : 0x0200 ,
140
150
}
141
151
152
+
142
153
class Homing (object ):
143
154
CW_START = 0x10
144
155
CW_HALT = 0x100
@@ -154,23 +165,23 @@ class Homing(object):
154
165
HM_NO_HOMING_OPERATION = 0
155
166
HM_ON_THE_NEGATIVE_LIMIT_SWITCH_AND_INDEX_PULSE = 1
156
167
HM_ON_THE_POSITIVE_LIMIT_SWITCH_AND_INDEX_PULSE = 2
157
- HM_ON_THE_POSITIVE_HOME_SWITCH_AND_INDEX_PULSE = [ 3 , 4 ]
158
- HM_ON_THE_NEGATIVE_HOME_SWITCH_AND_INDEX_PULSE = [ 5 , 6 ]
168
+ HM_ON_THE_POSITIVE_HOME_SWITCH_AND_INDEX_PULSE = ( 3 , 4 )
169
+ HM_ON_THE_NEGATIVE_HOME_SWITCH_AND_INDEX_PULSE = ( 5 , 6 )
159
170
HM_ON_THE_NEGATIVE_LIMIT_SWITCH = 17
160
171
HM_ON_THE_POSITIVE_LIMIT_SWITCH = 18
161
- HM_ON_THE_POSITIVE_HOME_SWITCH = [ 19 , 20 ]
162
- HM_ON_THE_NEGATIVE_HOME_SWITCH = [ 21 , 22 ]
172
+ HM_ON_THE_POSITIVE_HOME_SWITCH = ( 19 , 20 )
173
+ HM_ON_THE_NEGATIVE_HOME_SWITCH = ( 21 , 22 )
163
174
HM_ON_NEGATIVE_INDEX_PULSE = 33
164
175
HM_ON_POSITIVE_INDEX_PULSE = 34
165
176
HM_ON_CURRENT_POSITION = 35
166
177
167
178
STATES = {
168
- 'IN PROGRESS' : [ 0x3400 , 0x0000 ] ,
169
- 'INTERRUPTED' : [ 0x3400 , 0x0400 ] ,
170
- 'ATTAINED' : [ 0x3400 , 0x1000 ] ,
171
- 'TARGET REACHED' : [ 0x3400 , 0x1400 ] ,
172
- 'ERROR VELOCITY IS NOT ZERO' : [ 0x3400 , 0x2000 ] ,
173
- 'ERROR VELOCITY IS ZERO' : [ 0x3400 , 0x2400 ]
179
+ 'IN PROGRESS' : ( 0x3400 , 0x0000 ) ,
180
+ 'INTERRUPTED' : ( 0x3400 , 0x0400 ) ,
181
+ 'ATTAINED' : ( 0x3400 , 0x1000 ) ,
182
+ 'TARGET REACHED' : ( 0x3400 , 0x1400 ) ,
183
+ 'ERROR VELOCITY IS NOT ZERO' : ( 0x3400 , 0x2000 ) ,
184
+ 'ERROR VELOCITY IS ZERO' : ( 0x3400 , 0x2400 ),
174
185
}
175
186
176
187
@@ -185,10 +196,17 @@ class BaseNode402(RemoteNode):
185
196
:type object_dictionary: :class:`str`, :class:`canopen.ObjectDictionary`
186
197
"""
187
198
199
+ TIMEOUT_RESET_FAULT = 0.4 # seconds
200
+ TIMEOUT_SWITCH_OP_MODE = 0.5 # seconds
201
+ TIMEOUT_SWITCH_STATE_FINAL = 0.8 # seconds
202
+ TIMEOUT_SWITCH_STATE_SINGLE = 0.4 # seconds
203
+ INTERVAL_CHECK_STATE = 0.01 # seconds
204
+ TIMEOUT_HOMING_DEFAULT = 30 # seconds
205
+
188
206
def __init__ (self , node_id , object_dictionary ):
189
207
super (BaseNode402 , self ).__init__ (node_id , object_dictionary )
190
- self .tpdo_values = dict () # { index: TPDO_value }
191
- self .rpdo_pointers = dict () # { index: RPDO_pointer }
208
+ self .tpdo_values = dict () # { index: TPDO_value }
209
+ self .rpdo_pointers = dict () # { index: RPDO_pointer }
192
210
193
211
def setup_402_state_machine (self ):
194
212
"""Configure the state machine by searching for a TPDO that has the
@@ -204,7 +222,7 @@ def setup_402_state_machine(self):
204
222
self .state = 'SWITCH ON DISABLED' # Why change state?
205
223
206
224
def setup_pdos (self ):
207
- self .pdo .read () # TPDO and RPDO configurations
225
+ self .pdo .read () # TPDO and RPDO configurations
208
226
self ._init_tpdo_values ()
209
227
self ._init_rpdo_pointers ()
210
228
@@ -218,7 +236,7 @@ def _init_tpdo_values(self):
218
236
self .tpdo_values [obj .index ] = 0
219
237
220
238
def _init_rpdo_pointers (self ):
221
- # If RPDOs have overlapping indecies, rpdo_pointers will point to
239
+ # If RPDOs have overlapping indecies, rpdo_pointers will point to
222
240
# the first RPDO that has that index configured.
223
241
for rpdo in self .rpdo .values ():
224
242
if rpdo .enabled :
@@ -228,13 +246,13 @@ def _init_rpdo_pointers(self):
228
246
self .rpdo_pointers [obj .index ] = obj
229
247
230
248
def _check_controlword_configured (self ):
231
- if 0x6040 not in self .rpdo_pointers : # Controlword
249
+ if 0x6040 not in self .rpdo_pointers : # Controlword
232
250
logger .warning (
233
251
"Controlword not configured in node {0}'s PDOs. Using SDOs can cause slow performance." .format (
234
252
self .id ))
235
253
236
254
def _check_statusword_configured (self ):
237
- if 0x6041 not in self .tpdo_values : # Statusword
255
+ if 0x6041 not in self .tpdo_values : # Statusword
238
256
raise ValueError (
239
257
"Statusword not configured in node {0}'s PDOs. Using SDOs can cause slow performance." .format (
240
258
self .id ))
@@ -245,18 +263,18 @@ def reset_from_fault(self):
245
263
if self .state == 'FAULT' :
246
264
# Resets the Fault Reset bit (rising edge 0 -> 1)
247
265
self .controlword = State402 .CW_DISABLE_VOLTAGE
248
- timeout = time .time () + 0.4 # 400 ms
249
-
266
+ timeout = time .monotonic () + self .TIMEOUT_RESET_FAULT
250
267
while self .is_faulted ():
251
- if time .time () > timeout :
268
+ if time .monotonic () > timeout :
252
269
break
253
- time .sleep (0.01 ) # 10 ms
270
+ time .sleep (self . INTERVAL_CHECK_STATE )
254
271
self .state = 'OPERATION ENABLED'
255
-
272
+
256
273
def is_faulted (self ):
257
- return self .statusword & State402 .SW_MASK ['FAULT' ][0 ] == State402 .SW_MASK ['FAULT' ][1 ]
274
+ bitmask , bits = State402 .SW_MASK ['FAULT' ]
275
+ return self .statusword & bitmask == bits
258
276
259
- def homing (self , timeout = 30 , set_new_home = True ):
277
+ def homing (self , timeout = TIMEOUT_HOMING_DEFAULT , set_new_home = True ):
260
278
"""Function to execute the configured Homing Method on the node
261
279
:param int timeout: Timeout value (default: 30)
262
280
:param bool set_new_home: Defines if the node should set the home offset
@@ -271,23 +289,24 @@ def homing(self, timeout=30, set_new_home=True):
271
289
self .state = 'OPERATION ENABLED'
272
290
homingstatus = 'IN PROGRESS'
273
291
self .controlword = State402 .CW_OPERATION_ENABLED | Homing .CW_START
274
- t = time .time () + timeout
292
+ t = time .monotonic () + timeout
275
293
try :
276
294
while homingstatus not in ('TARGET REACHED' , 'ATTAINED' ):
277
295
for key , value in Homing .STATES .items ():
278
- # check if the value after applying the bitmask (value[0])
279
- # corresponds with the value[1] to determine the current status
280
- bitmaskvalue = self . statusword & value [ 0 ]
281
- if bitmaskvalue == value [ 1 ] :
296
+ # check if the Statusword after applying the bitmask
297
+ # corresponds with the needed bits to determine the current status
298
+ bitmask , bits = value
299
+ if self . statusword & bitmask == bits :
282
300
homingstatus = key
283
- if homingstatus in ('INTERRUPTED' , 'ERROR VELOCITY IS NOT ZERO' , 'ERROR VELOCITY IS ZERO' ):
284
- raise RuntimeError ('Unable to home. Reason: {0}' .format (homingstatus ))
285
- time .sleep (0.001 )
286
- if time .time () > t :
301
+ if homingstatus in ('INTERRUPTED' , 'ERROR VELOCITY IS NOT ZERO' ,
302
+ 'ERROR VELOCITY IS ZERO' ):
303
+ raise RuntimeError ('Unable to home. Reason: {0}' .format (homingstatus ))
304
+ time .sleep (self .INTERVAL_CHECK_STATE )
305
+ if time .monotonic () > t :
287
306
raise RuntimeError ('Unable to home, timeout reached' )
288
307
if set_new_home :
289
308
actual_position = self .sdo [0x6063 ].raw
290
- self .sdo [0x607C ].raw = actual_position # home offset (0x607C)
309
+ self .sdo [0x607C ].raw = actual_position # Home Offset
291
310
logger .info ('Homing offset set to {0}' .format (actual_position ))
292
311
logger .info ('Homing mode carried out successfully.' )
293
312
return True
@@ -334,16 +353,16 @@ def op_mode(self, mode):
334
353
start_state = self .state
335
354
336
355
if self .state == 'OPERATION ENABLED' :
337
- self .state = 'SWITCHED ON'
356
+ self .state = 'SWITCHED ON'
338
357
# ensure the node does not move with an old value
339
358
self ._clear_target_values () # Shouldn't this happen before it's switched on?
340
359
341
360
# operation mode
342
361
self .sdo [0x6060 ].raw = OperationMode .NAME2CODE [mode ]
343
362
344
- timeout = time .time () + 0.5 # 500 ms
363
+ timeout = time .monotonic () + self . TIMEOUT_SWITCH_OP_MODE
345
364
while self .op_mode != mode :
346
- if time .time () > timeout :
365
+ if time .monotonic () > timeout :
347
366
raise RuntimeError (
348
367
"Timeout setting node {0}'s new mode of operation to {1}." .format (
349
368
self .id , mode ))
@@ -354,7 +373,7 @@ def op_mode(self, mode):
354
373
logger .warning ('{0}' .format (str (e )))
355
374
finally :
356
375
self .state = start_state # why?
357
- logger .info ('Set node {n} operation mode to {m}.' .format (n = self .id , m = mode ))
376
+ logger .info ('Set node {n} operation mode to {m}.' .format (n = self .id , m = mode ))
358
377
return False
359
378
360
379
def _clear_target_values (self ):
@@ -421,10 +440,8 @@ def state(self):
421
440
- 'QUICK STOP ACTIVE'
422
441
"""
423
442
for state , mask_val_pair in State402 .SW_MASK .items ():
424
- mask = mask_val_pair [0 ]
425
- state_value = mask_val_pair [1 ]
426
- masked_value = self .statusword & mask
427
- if masked_value == state_value :
443
+ bitmask , bits = mask_val_pair
444
+ if self .statusword & bitmask == bits :
428
445
return state
429
446
return 'UNKNOWN'
430
447
@@ -442,14 +459,14 @@ def state(self, target_state):
442
459
:raise RuntimeError: Occurs when the time defined to change the state is reached
443
460
:raise ValueError: Occurs when trying to execute a ilegal transition in the sate machine
444
461
"""
445
- timeout = time .time () + 0.8 # 800 ms
462
+ timeout = time .monotonic () + self . TIMEOUT_SWITCH_STATE_FINAL
446
463
while self .state != target_state :
447
464
next_state = self ._next_state (target_state )
448
465
if self ._change_state (next_state ):
449
- continue
450
- if time .time () > timeout :
466
+ continue
467
+ if time .monotonic () > timeout :
451
468
raise RuntimeError ('Timeout when trying to change state' )
452
- time .sleep (0.01 ) # 10 ms
469
+ time .sleep (self . INTERVAL_CHECK_STATE )
453
470
454
471
def _next_state (self , target_state ):
455
472
if target_state == 'OPERATION ENABLED' :
@@ -463,9 +480,9 @@ def _change_state(self, target_state):
463
480
except KeyError :
464
481
raise ValueError (
465
482
'Illegal state transition from {f} to {t}' .format (f = self .state , t = target_state ))
466
- timeout = time .time () + 0.4 # 400 ms
483
+ timeout = time .monotonic () + self . TIMEOUT_SWITCH_STATE_SINGLE
467
484
while self .state != target_state :
468
- if time .time () > timeout :
485
+ if time .monotonic () > timeout :
469
486
return False
470
- time .sleep (0.01 ) # 10 ms
487
+ time .sleep (self . INTERVAL_CHECK_STATE )
471
488
return True
0 commit comments