Skip to content

Commit cef9cbe

Browse files
henrikbrixandersencfriedt
authored andcommitted
runners: canopen: poll for flash ready
Poll the flash status instead of just reading the flash status once. Add support for controlling the number of SDO retries and the SDO timeouts. These changes allows for greater control of the CANopen program download, which is especially useful on noisy or congested CAN networks and on devices with slower flash access. Fixes: #39409 Signed-off-by: Klaus H. Sorensen <[email protected]> Signed-off-by: Henrik Brix Andersen <[email protected]>
1 parent ac0477f commit cef9cbe

File tree

2 files changed

+75
-23
lines changed

2 files changed

+75
-23
lines changed

scripts/west_commands/runners/canopen_program.py

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import argparse
88
import os
9+
import time
910

1011
from runners.core import ZephyrBinaryRunner, RunnerCaps
1112

@@ -19,6 +20,14 @@
1920
# Default Python-CAN context to use, see python-can documentation for details
2021
DEFAULT_CAN_CONTEXT = 'default'
2122

23+
# Default program number
24+
DEFAULT_PROGRAM_NUMBER = 1
25+
26+
# Default timeouts and retries
27+
DEFAULT_TIMEOUT = 10.0 # seconds
28+
DEFAULT_SDO_TIMEOUT = 0.3 # seconds
29+
DEFAULT_SDO_RETRIES = 1
30+
2231
# Object dictionary indexes
2332
H1F50_PROGRAM_DATA = 0x1F50
2433
H1F51_PROGRAM_CTRL = 0x1F51
@@ -40,8 +49,9 @@ def __call__(self, parser, namespace, values, option_string=None):
4049
class CANopenBinaryRunner(ZephyrBinaryRunner):
4150
'''Runner front-end for CANopen.'''
4251
def __init__(self, cfg, node_id, can_context=DEFAULT_CAN_CONTEXT,
43-
program_number=1, confirm=True,
44-
confirm_only=True, timeout=10):
52+
program_number=DEFAULT_PROGRAM_NUMBER, confirm=True,
53+
confirm_only=True, timeout=DEFAULT_TIMEOUT,
54+
sdo_retries=DEFAULT_SDO_RETRIES, sdo_timeout=DEFAULT_SDO_TIMEOUT):
4555
if MISSING_REQUIREMENTS:
4656
raise RuntimeError('one or more Python dependencies were missing; '
4757
"see the getting started guide for details on "
@@ -55,7 +65,9 @@ def __init__(self, cfg, node_id, can_context=DEFAULT_CAN_CONTEXT,
5565
self.downloader = CANopenProgramDownloader(logger=self.logger,
5666
node_id=node_id,
5767
can_context=can_context,
58-
program_number=program_number)
68+
program_number=program_number,
69+
sdo_retries=sdo_retries,
70+
sdo_timeout=sdo_timeout)
5971

6072
@classmethod
6173
def name(cls):
@@ -72,28 +84,35 @@ def do_add_parser(cls, parser):
7284

7385
# Optional:
7486
parser.add_argument('--can-context', default=DEFAULT_CAN_CONTEXT,
75-
help='Custom Python-CAN context to use')
76-
parser.add_argument('--program-number', default=1,
77-
help='program number, default is 1')
87+
help=f'Python-CAN context to use (default: {DEFAULT_CAN_CONTEXT})')
88+
parser.add_argument('--program-number', type=int, default=DEFAULT_PROGRAM_NUMBER,
89+
help=f'program number (default: {DEFAULT_PROGRAM_NUMBER})')
7890
parser.add_argument('--confirm', '--no-confirm',
7991
dest='confirm', nargs=0,
8092
action=ToggleAction,
8193
help='confirm after starting? (default: yes)')
8294
parser.add_argument('--confirm-only', default=False, action='store_true',
8395
help='confirm only, no program download (default: no)')
84-
parser.add_argument('--timeout', default=10,
85-
help='boot-up timeout, default is 10 seconds')
96+
parser.add_argument('--timeout', type=float, default=DEFAULT_TIMEOUT,
97+
help=f'Timeout in seconds (default: {DEFAULT_TIMEOUT})')
98+
parser.add_argument('--sdo-retries', type=int, default=DEFAULT_SDO_RETRIES,
99+
help=f'CANopen SDO request retries (default: {DEFAULT_SDO_RETRIES})')
100+
parser.add_argument('--sdo-timeout', type=float, default=DEFAULT_SDO_TIMEOUT,
101+
help=f'''CANopen SDO response timeout in seconds
102+
(default: {DEFAULT_SDO_TIMEOUT})''')
86103

87104
parser.set_defaults(confirm=True)
88105

89106
@classmethod
90107
def do_create(cls, cfg, args):
91108
return CANopenBinaryRunner(cfg, int(args.node_id),
92109
can_context=args.can_context,
93-
program_number=int(args.program_number),
110+
program_number=args.program_number,
94111
confirm=args.confirm,
95112
confirm_only=args.confirm_only,
96-
timeout=int(args.timeout))
113+
timeout=args.timeout,
114+
sdo_retries=args.sdo_retries,
115+
sdo_timeout=args.sdo_timeout)
97116

98117
def do_run(self, command, **kwargs):
99118
if command == 'flash':
@@ -107,7 +126,7 @@ def flash(self, **kwargs):
107126
self.downloader.program_number)
108127

109128
self.downloader.connect()
110-
status = self.downloader.flash_status()
129+
status = self.downloader.wait_for_flash_status_ok(self.timeout)
111130
if status == 0:
112131
self.downloader.swid()
113132
else:
@@ -126,13 +145,15 @@ def flash(self, **kwargs):
126145

127146
self.downloader.stop_program()
128147
self.downloader.clear_program()
148+
self.downloader.wait_for_flash_status_ok(self.timeout)
129149
self.downloader.download(self.bin_file)
130150

131-
status = self.downloader.flash_status()
151+
status = self.downloader.wait_for_flash_status_ok(self.timeout)
132152
if status != 0:
133153
raise ValueError('Program download failed: '
134154
'flash status 0x{:02x}'.format(status))
135155

156+
self.downloader.swid()
136157
self.downloader.start_program()
137158
self.downloader.wait_for_bootup(self.timeout)
138159
self.downloader.swid()
@@ -146,7 +167,8 @@ def flash(self, **kwargs):
146167
class CANopenProgramDownloader(object):
147168
'''CANopen program downloader'''
148169
def __init__(self, logger, node_id, can_context=DEFAULT_CAN_CONTEXT,
149-
program_number=1):
170+
program_number=DEFAULT_PROGRAM_NUMBER,
171+
sdo_retries=DEFAULT_SDO_RETRIES, sdo_timeout=DEFAULT_SDO_TIMEOUT):
150172
super(CANopenProgramDownloader, self).__init__()
151173
self.logger = logger
152174
self.node_id = node_id
@@ -160,6 +182,9 @@ def __init__(self, logger, node_id, can_context=DEFAULT_CAN_CONTEXT,
160182
self.swid_sdo = self.node.sdo[H1F56_PROGRAM_SWID][self.program_number]
161183
self.flash_sdo = self.node.sdo[H1F57_FLASH_STATUS][self.program_number]
162184

185+
self.node.sdo.MAX_RETRIES = sdo_retries
186+
self.node.sdo.RESPONSE_TIMEOUT = sdo_timeout
187+
163188
def connect(self):
164189
'''Connect to CAN network'''
165190
try:
@@ -238,20 +263,36 @@ def download(self, bin_file):
238263
break
239264
outfile.write(chunk)
240265
progress.next(n=len(chunk))
266+
except:
267+
raise ValueError('Failed to download program')
268+
finally:
241269
progress.finish()
242270
infile.close()
243271
outfile.close()
244-
except:
245-
raise ValueError('Failed to download program')
246272

247-
def wait_for_bootup(self, timeout=10):
273+
def wait_for_bootup(self, timeout=DEFAULT_TIMEOUT):
248274
'''Wait for boot-up message reception'''
249275
self.logger.info('Waiting for boot-up message...')
250276
try:
251277
self.node.nmt.wait_for_bootup(timeout=timeout)
252278
except:
253279
raise ValueError('Timeout waiting for boot-up message')
254280

281+
def wait_for_flash_status_ok(self, timeout=DEFAULT_TIMEOUT):
282+
'''Wait for flash status ok'''
283+
self.logger.info('Waiting for flash status ok')
284+
end_time = time.time() + timeout
285+
while True:
286+
now = time.time()
287+
status = self.flash_status()
288+
if status == 0:
289+
break
290+
291+
if now > end_time:
292+
return status
293+
294+
return status
295+
255296
@staticmethod
256297
def create_object_dictionary():
257298
'''Create a synthetic CANopen object dictionary for program download'''

scripts/west_commands/tests/test_canopen_program.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
# Test cases
2323
#
2424

25-
TEST_CASES = [(n, x, p, c, o, t)
25+
TEST_CASES = [(n, x, p, c, o, t, r, s)
2626
for n in range(1, 3)
2727
for x in (None, TEST_ALT_CONTEXT)
2828
for p in range(1, 3)
2929
for c in (False, True)
3030
for o in (False, True)
31-
for t in range(1, 3)]
31+
for t in range(1, 3)
32+
for r in range(1, 3)
33+
for s in range(1, 3)]
3234

3335
os_path_isfile = os.path.isfile
3436

@@ -41,7 +43,7 @@ def os_path_isfile_patch(filename):
4143
@patch('runners.canopen_program.CANopenProgramDownloader')
4244
def test_canopen_program_create(cpd, test_case, runner_config):
4345
'''Test CANopen runner created from command line parameters.'''
44-
node_id, context, program_number, confirm, confirm_only, timeout = test_case
46+
node_id, context, program_number, confirm, confirm_only, timeout, sdo_retries, sdo_timeout = test_case
4547

4648
args = ['--node-id', str(node_id)]
4749
if context is not None:
@@ -54,9 +56,14 @@ def test_canopen_program_create(cpd, test_case, runner_config):
5456
args.append('--confirm-only')
5557
if timeout:
5658
args.extend(['--timeout', str(timeout)])
59+
if sdo_retries:
60+
args.extend(['--sdo-retries', str(sdo_retries)])
61+
if sdo_timeout:
62+
args.extend(['--sdo-timeout', str(sdo_timeout)])
5763

5864
mock = cpd.return_value
5965
mock.flash_status.return_value = 0
66+
mock.wait_for_flash_status_ok.return_value = 0
6067
mock.swid.return_value = 0
6168

6269
parser = argparse.ArgumentParser()
@@ -71,17 +78,21 @@ def test_canopen_program_create(cpd, test_case, runner_config):
7178
assert cpd.call_args == call(node_id=node_id,
7279
can_context=context,
7380
logger=runner.logger,
74-
program_number=program_number)
81+
program_number=program_number,
82+
sdo_retries=sdo_retries,
83+
sdo_timeout=sdo_timeout)
7584
else:
7685
assert cpd.call_args == call(node_id=node_id,
7786
can_context=TEST_DEF_CONTEXT,
7887
logger=runner.logger,
79-
program_number=program_number)
88+
program_number=program_number,
89+
sdo_retries=sdo_retries,
90+
sdo_timeout=sdo_timeout)
8091

8192
mock.connect.assert_called_once()
8293

8394
if confirm_only:
84-
mock.flash_status.assert_called_once()
95+
mock.wait_for_flash_status_ok.assert_called_with(timeout)
8596
mock.swid.assert_called_once()
8697
mock.enter_pre_operational.assert_called_once()
8798
mock.zephyr_confirm_program.assert_called_once()
@@ -92,7 +103,7 @@ def test_canopen_program_create(cpd, test_case, runner_config):
92103
mock.wait_for_bootup.assert_not_called()
93104
else:
94105
mock.enter_pre_operational.assert_called()
95-
mock.flash_status.assert_called()
106+
mock.wait_for_flash_status_ok.assert_called_with(timeout)
96107
mock.swid.assert_called()
97108
mock.stop_program.assert_called_once()
98109
mock.clear_program.assert_called_once()

0 commit comments

Comments
 (0)