Skip to content

Commit 5202d1e

Browse files
authored
DS402: Fix logic errors in the power state machine and generalize it. (#264)
* p402: Forbid transitions to states which only the drive can enter. The NOT READY TO SWITCH ON and FAULT REACTION ACTIVE states can only be reached if the drive encounters an error or is still busy with its self-testing. Raise an exception in case these states are requested via the property. In extension, only the drive can trigger a transition to the FAULT state, so that is never valid as an end goal requested by the user. * p402: Add a test script to check DS402 State Machine transitions. Go through all possible target states and display where the next_state_for_enabling() function would lead to based on the original state. Mark transitions which can happen directly because they are in the TRANSITIONTABLE. * p402: Extend test script to check the actual implementation. Mock up a BaseNode402 object and compare the _next_state() behavior to the simple lookups from the underlying transition tables. * p402: Simplify check for intermediate states. Don't special case the OPERATION ENABLED state, as the mechanism formulated for it is actually usable for almost all states. Instead, check whether there is a direct transition and return that immediately before consulting next_state_for_enabling(). * p402: Rename NEXTSTATE2ENABLE to generalize it. The goal is to provide automatic transition paths for any state, not only OPERATION ENABLED. Reflect that in the naming, without changing the actual logic yet. * p402: Adjust automatic state transition paths. As there is a direct transition from QUICK STOP ACTIVE to OPERATION ENABLED, remove that from the transition paths. Instead go through the SWITCH ON DISABLED state, closing the cycle to make it work for anything between SWITCH ON DISABLED and OPERATION ENABLED. Also remove the self-reference OPERATION ENABLED to itself, which is useless. The whole state changing code will only be called if the target state and the current state do not match. * p402: Remove two illegal transitions from the state table. Transitions 7 and 10 are duplicated and certainly wrong in the quickstop context. The only transition toward QUICK STOP ACTIVE is from OPERATION ENABLED. * Move test_p402_states script to a subdirectory and add a docstring.
1 parent 33f7af3 commit 5202d1e

File tree

2 files changed

+59
-13
lines changed

2 files changed

+59
-13
lines changed

canopen/profiles/p402.py

+22-13
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ class State402(object):
4646
'QUICK STOP ACTIVE': (0x6F, 0x07),
4747
}
4848

49-
# Transition path to get to the 'OPERATION ENABLED' state
50-
NEXTSTATE2ENABLE = {
49+
# Transition path to reach and state without a direct transition
50+
NEXTSTATE2ANY = {
5151
('START'): 'NOT READY TO SWITCH ON',
52-
('FAULT', 'NOT READY TO SWITCH ON'): 'SWITCH ON DISABLED',
52+
('FAULT', 'NOT READY TO SWITCH ON', 'QUICK STOP ACTIVE'): 'SWITCH ON DISABLED',
5353
('SWITCH ON DISABLED'): 'READY TO SWITCH ON',
5454
('READY TO SWITCH ON'): 'SWITCHED ON',
55-
('SWITCHED ON', 'QUICK STOP ACTIVE', 'OPERATION ENABLED'): 'OPERATION ENABLED',
56-
('FAULT REACTION ACTIVE'): 'FAULT'
55+
('SWITCHED ON'): 'OPERATION ENABLED',
56+
('FAULT REACTION ACTIVE'): 'FAULT',
5757
}
5858

5959
# Tansition table from the DS402 State Machine
@@ -78,22 +78,25 @@ class State402(object):
7878
('SWITCHED ON', 'OPERATION ENABLED'): CW_OPERATION_ENABLED, # transition 4
7979
('QUICK STOP ACTIVE', 'OPERATION ENABLED'): CW_OPERATION_ENABLED, # transition 16
8080
# quickstop ---------------------------------------------------------------------------
81-
('READY TO SWITCH ON', 'QUICK STOP ACTIVE'): CW_QUICK_STOP, # transition 7
82-
('SWITCHED ON', 'QUICK STOP ACTIVE'): CW_QUICK_STOP, # transition 10
8381
('OPERATION ENABLED', 'QUICK STOP ACTIVE'): CW_QUICK_STOP, # transition 11
8482
# fault -------------------------------------------------------------------------------
8583
('FAULT', 'SWITCH ON DISABLED'): CW_SWITCH_ON_DISABLED, # transition 15
8684
}
8785

8886
@staticmethod
89-
def next_state_for_enabling(_from):
90-
"""Return the next state needed for reach the state Operation Enabled.
87+
def next_state_indirect(_from):
88+
"""Return the next state needed to reach any state indirectly.
89+
90+
The chosen path always points toward the OPERATION ENABLED state, except when
91+
coming from QUICK STOP ACTIVE. In that case, it will cycle through SWITCH ON
92+
DISABLED first, as there would have been a direct transition if the opposite was
93+
desired.
9194
9295
:param str target: Target state.
9396
:return: Next target to change.
9497
:rtype: str
9598
"""
96-
for cond, next_state in State402.NEXTSTATE2ENABLE.items():
99+
for cond, next_state in State402.NEXTSTATE2ANY.items():
97100
if _from in cond:
98101
return next_state
99102

@@ -551,10 +554,16 @@ def state(self, target_state):
551554
self.check_statusword()
552555

553556
def _next_state(self, target_state):
554-
if target_state == 'OPERATION ENABLED':
555-
return State402.next_state_for_enabling(self.state)
556-
else:
557+
if target_state in ('NOT READY TO SWITCH ON',
558+
'FAULT REACTION ACTIVE',
559+
'FAULT'):
560+
raise ValueError(
561+
'Target state {} cannot be entered programmatically'.format(target_state))
562+
from_state = self.state
563+
if (from_state, target_state) in State402.TRANSITIONTABLE:
557564
return target_state
565+
else:
566+
return State402.next_state_indirect(from_state)
558567

559568
def _change_state(self, target_state):
560569
try:
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Verification script to diagnose automatic state transitions.
2+
3+
This is meant to be run for verifying changes to the DS402 power state
4+
machine code. For each target state, it just lists the next
5+
intermediate state which would be set automatically, depending on the
6+
assumed current state.
7+
"""
8+
9+
from canopen.objectdictionary import ObjectDictionary
10+
from canopen.profiles.p402 import State402, BaseNode402
11+
12+
13+
if __name__ == '__main__':
14+
n = BaseNode402(1, ObjectDictionary())
15+
16+
for target_state in State402.SW_MASK:
17+
print('\n--- Target =', target_state, '---')
18+
for from_state in State402.SW_MASK:
19+
if target_state == from_state:
20+
continue
21+
if (from_state, target_state) in State402.TRANSITIONTABLE:
22+
print('direct:\t{} -> {}'.format(from_state, target_state))
23+
else:
24+
next_state = State402.next_state_indirect(from_state)
25+
if not next_state:
26+
print('FAIL:\t{} -> {}'.format(from_state, next_state))
27+
else:
28+
print('\t{} -> {} ...'.format(from_state, next_state))
29+
30+
try:
31+
while from_state != target_state:
32+
n.tpdo_values[0x6041] = State402.SW_MASK[from_state][1]
33+
next_state = n._next_state(target_state)
34+
print('\t\t-> {}'.format(next_state))
35+
from_state = next_state
36+
except ValueError:
37+
print('\t\t-> disallowed!')

0 commit comments

Comments
 (0)