Skip to content

Commit 09f6e9e

Browse files
Gracefully handle readonly entries in PdoMap.save() (fixes #541) (#593)
* Skip saving and log read-only PDO parameters. For PDO communication or mapping parameters which are read-only on a node, the PdoMap.save() method currently aborts with an SDO exception. However, individual parameters may simply not support configuration changes. Rely on the Object Dictionary to tell which ones are expected to fail and skip them. For communication record entries, log what is not being saved. * Switch to a LocalNode in TestPDO to not require a network. * Test PdoMap.save() skipping readonly entries. Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent 8d9aa6c commit 09f6e9e

File tree

2 files changed

+47
-25
lines changed

2 files changed

+47
-25
lines changed

canopen/pdo/base.py

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -403,19 +403,27 @@ def save(self) -> None:
403403
logger.info("Skip saving %s: COB-ID was never set", self.com_record.od.name)
404404
return
405405
logger.info("Setting COB-ID 0x%X and temporarily disabling PDO", self.cob_id)
406-
self.com_record[1].raw = self.cob_id | PDO_NOT_VALID | (RTR_NOT_ALLOWED if not self.rtr_allowed else 0x0)
407-
if self.trans_type is not None:
408-
logger.info("Setting transmission type to %d", self.trans_type)
409-
self.com_record[2].raw = self.trans_type
410-
if self.inhibit_time is not None:
411-
logger.info("Setting inhibit time to %d us", (self.inhibit_time * 100))
412-
self.com_record[3].raw = self.inhibit_time
413-
if self.event_timer is not None:
414-
logger.info("Setting event timer to %d ms", self.event_timer)
415-
self.com_record[5].raw = self.event_timer
416-
if self.sync_start_value is not None:
417-
logger.info("Setting SYNC start value to %d", self.sync_start_value)
418-
self.com_record[6].raw = self.sync_start_value
406+
self.com_record[1].raw = (
407+
self.cob_id
408+
| PDO_NOT_VALID
409+
| (RTR_NOT_ALLOWED if not self.rtr_allowed else 0)
410+
)
411+
412+
def _set_com_record(
413+
subindex: int, value: Optional[int], log_fmt: str, log_factor: int = 1
414+
):
415+
if value is None:
416+
return
417+
if self.com_record[subindex].writable:
418+
logger.info(f"Setting {log_fmt}", value * log_factor)
419+
self.com_record[subindex].raw = value
420+
else:
421+
logger.info(f"Cannot set {log_fmt}, not writable", value * log_factor)
422+
423+
_set_com_record(2, self.trans_type, "transmission type to %d")
424+
_set_com_record(3, self.inhibit_time, "inhibit time to %d us", 100)
425+
_set_com_record(5, self.event_timer, "event timer to %d ms")
426+
_set_com_record(6, self.sync_start_value, "SYNC start value to %d")
419427

420428
try:
421429
self.map_array[0].raw = 0
@@ -425,20 +433,21 @@ def save(self) -> None:
425433
# mappings for an invalid object 0x0000:00 to overwrite any
426434
# excess entries with all-zeros.
427435
self._fill_map(self.map_array[0].raw)
428-
subindex = 1
429-
for var in self.map:
430-
logger.info("Writing %s (0x%04X:%02X, %d bits) to PDO map",
431-
var.name, var.index, var.subindex, var.length)
436+
for var, entry in zip(self.map, self.map_array.values()):
437+
if not entry.od.writable:
438+
continue
439+
logger.info(
440+
"Writing %s (0x%04X:%02X, %d bits) to PDO map",
441+
var.name,
442+
var.index,
443+
var.subindex,
444+
var.length,
445+
)
432446
if getattr(self.pdo_node.node, "curtis_hack", False):
433447
# Curtis HACK: mixed up field order
434-
self.map_array[subindex].raw = (var.index |
435-
var.subindex << 16 |
436-
var.length << 24)
448+
entry.raw = var.index | var.subindex << 16 | var.length << 24
437449
else:
438-
self.map_array[subindex].raw = (var.index << 16 |
439-
var.subindex << 8 |
440-
var.length)
441-
subindex += 1
450+
entry.raw = var.index << 16 | var.subindex << 8 | var.length
442451
try:
443452
self.map_array[0].raw = len(self.map)
444453
except SdoAbortedError as e:

test/test_pdo.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class TestPDO(unittest.TestCase):
99
def setUp(self):
10-
node = canopen.Node(1, SAMPLE_EDS)
10+
node = canopen.LocalNode(1, SAMPLE_EDS)
1111
pdo = node.pdo.tx[1]
1212
pdo.add_variable('INTEGER16 value') # 0x2001
1313
pdo.add_variable('UNSIGNED8 value', length=4) # 0x2002
@@ -64,6 +64,19 @@ def test_pdo_save(self):
6464
self.node.tpdo.save()
6565
self.node.rpdo.save()
6666

67+
def test_pdo_save_skip_readonly(self):
68+
"""Expect no exception when a record entry is not writable."""
69+
# Saving only happens with a defined COB ID and for specified parameters
70+
self.node.tpdo[1].cob_id = self.node.tpdo[1].predefined_cob_id
71+
self.node.tpdo[1].trans_type = 1
72+
self.node.tpdo[1].map_array[1].od.access_type = "r"
73+
self.node.tpdo[1].save()
74+
75+
self.node.tpdo[2].cob_id = self.node.tpdo[2].predefined_cob_id
76+
self.node.tpdo[2].trans_type = 1
77+
self.node.tpdo[2].com_record[2].od.access_type = "r"
78+
self.node.tpdo[2].save()
79+
6780
def test_pdo_export(self):
6881
try:
6982
import canmatrix

0 commit comments

Comments
 (0)