From b133a4021d13c15f1e050d8599dd6b0241afa4c7 Mon Sep 17 00:00:00 2001 From: pankaj <0xpankaj@gmail.com> Date: Fri, 25 Sep 2026 04:29:23 +0545 Subject: [PATCH] pdo: Write the entries of an ARRAY mapping in PdoMap.save() save() sets sub-index 0 of the mapping to 0 and then iterates self.map_array.values() to write the entries. For a mapping declared as an ARRAY, map_array is an SdoArray whose length is uploaded from sub-index 0, which was just set to 0, so no entry is written and only the count is. Address the entries by the sub-indices the object dictionary defines instead. RECORD mappings and the fixed-count workaround behave as before. --- canopen/pdo/base.py | 8 +++++++- test/test_pdo.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/canopen/pdo/base.py b/canopen/pdo/base.py index f9973882..7c899142 100644 --- a/canopen/pdo/base.py +++ b/canopen/pdo/base.py @@ -452,7 +452,13 @@ def _set_com_record( # mappings for an invalid object 0x0000:00 to overwrite any # excess entries with all-zeros. self._fill_map(self.map_array[0].raw) - for var, entry in zip(self.map, self.map_array.values()): + # Address the entries by the sub-indices the object dictionary defines: + # iterating the SDO array would ask the device for its entry count, + # which was just set to 0 above, and so write no entry at all. + entries = ( + self.map_array[subindex] for subindex in self.map_array.od if subindex != 0 + ) + for var, entry in zip(self.map, entries): if not entry.od.writable: continue logger.info( diff --git a/test/test_pdo.py b/test/test_pdo.py index 1b641147..85caff41 100644 --- a/test/test_pdo.py +++ b/test/test_pdo.py @@ -110,6 +110,41 @@ def test_pdo_save(self): self.node.tpdo.save() self.node.rpdo.save() + def test_pdo_save_writes_the_entries_of_an_array_mapping(self): + # A mapping declared as an ARRAY is sized by its sub-index 0, which + # save() sets to 0 before it writes the entries. + od = canopen.objectdictionary.ObjectDictionary() + comm = canopen.objectdictionary.ODRecord("TPDO 1 communication", 0x1800) + for subindex, data_type in ((0, canopen.objectdictionary.UNSIGNED8), + (1, canopen.objectdictionary.UNSIGNED32), + (2, canopen.objectdictionary.UNSIGNED8)): + var = canopen.objectdictionary.ODVariable(f"sub{subindex}", 0x1800, subindex) + var.data_type, var.access_type, var.default = data_type, "rw", 0 + comm.add_member(var) + od.add_object(comm) + mapping = canopen.objectdictionary.ODArray("TPDO 1 mapping", 0x1A00) + for subindex in range(5): + var = canopen.objectdictionary.ODVariable(f"sub{subindex}", 0x1A00, subindex) + var.data_type = (canopen.objectdictionary.UNSIGNED8 if subindex == 0 + else canopen.objectdictionary.UNSIGNED32) + var.access_type, var.default = "rw", 0 + mapping.add_member(var) + od.add_object(mapping) + for index, data_type in ((0x2001, canopen.objectdictionary.INTEGER16), + (0x2002, canopen.objectdictionary.UNSIGNED8)): + var = canopen.objectdictionary.ODVariable(f"value {index:X}", index) + var.data_type, var.access_type, var.pdo_mappable = data_type, "rw", True + od.add_object(var) + node = canopen.LocalNode(1, od) + pdo = node.tpdo[1] + pdo.add_variable(0x2001) + pdo.add_variable(0x2002, length=4) + pdo.cob_id = 0x181 + pdo.save() + self.assertEqual(node.sdo.upload(0x1A00, 0), bytes([2])) + self.assertEqual(node.sdo.upload(0x1A00, 1), (0x20010010).to_bytes(4, "little")) + self.assertEqual(node.sdo.upload(0x1A00, 2), (0x20020004).to_bytes(4, "little")) + def test_pdo_save_skip_readonly(self): """Expect no exception when a record entry is not writable.""" # Saving only happens with a defined COB ID and for specified parameters