diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 8fbd0aca..03a8c826 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -17,6 +17,7 @@ jobs: fail-fast: false matrix: python-version: ['3.x'] + features: ['', '[db_export]'] steps: - uses: actions/checkout@v3 @@ -28,7 +29,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pytest pytest-cov - pip install -e . + pip install -e '.${{ matrix.features }}' - name: Test with pytest run: | pytest -v --cov=canopen --cov-report=xml --cov-branch diff --git a/canopen/pdo/base.py b/canopen/pdo/base.py index def74ff0..f2a7d205 100644 --- a/canopen/pdo/base.py +++ b/canopen/pdo/base.py @@ -78,14 +78,24 @@ def subscribe(self): def export(self, filename): """Export current configuration to a database file. + .. note:: + This API requires the ``db_export`` feature to be installed:: + + python3 -m pip install 'canopen[db_export]' + :param str filename: Filename to save to (e.g. DBC, DBF, ARXML, KCD etc) + :raises NotImplementedError: + When the ``canopen[db_export]`` feature is not installed. :return: The CanMatrix object created :rtype: canmatrix.canmatrix.CanMatrix """ - from canmatrix import canmatrix - from canmatrix import formats + try: + from canmatrix import canmatrix + from canmatrix import formats + except ImportError: + raise NotImplementedError("This feature requires the 'canopen[db_export]' feature") db = canmatrix.CanMatrix() for pdo_map in self.map.values(): diff --git a/pyproject.toml b/pyproject.toml index 67a435d8..a87ba589 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,11 @@ dependencies = [ ] dynamic = ["version"] +[project.optional-dependencies] +db_export = [ + "canmatrix ~= 1.0", +] + [project.urls] documentation = "https://canopen.readthedocs.io/en/stable/" repository = "https://github.com/christiansandberg/canopen" diff --git a/test/test_pdo.py b/test/test_pdo.py index 7e9947f1..32c0f174 100644 --- a/test/test_pdo.py +++ b/test/test_pdo.py @@ -5,36 +5,41 @@ class TestPDO(unittest.TestCase): - - def test_bit_mapping(self): + def setUp(self): node = canopen.Node(1, SAMPLE_EDS) - map = node.pdo.tx[1] - map.add_variable('INTEGER16 value') # 0x2001 - map.add_variable('UNSIGNED8 value', length=4) # 0x2002 - map.add_variable('INTEGER8 value', length=4) # 0x2003 - map.add_variable('INTEGER32 value') # 0x2004 - map.add_variable('BOOLEAN value', length=1) # 0x2005 - map.add_variable('BOOLEAN value 2', length=1) # 0x2006 + pdo = node.pdo.tx[1] + pdo.add_variable('INTEGER16 value') # 0x2001 + pdo.add_variable('UNSIGNED8 value', length=4) # 0x2002 + pdo.add_variable('INTEGER8 value', length=4) # 0x2003 + pdo.add_variable('INTEGER32 value') # 0x2004 + pdo.add_variable('BOOLEAN value', length=1) # 0x2005 + pdo.add_variable('BOOLEAN value 2', length=1) # 0x2006 # Write some values - map['INTEGER16 value'].raw = -3 - map['UNSIGNED8 value'].raw = 0xf - map['INTEGER8 value'].raw = -2 - map['INTEGER32 value'].raw = 0x01020304 - map['BOOLEAN value'].raw = False - map['BOOLEAN value 2'].raw = True + pdo['INTEGER16 value'].raw = -3 + pdo['UNSIGNED8 value'].raw = 0xf + pdo['INTEGER8 value'].raw = -2 + pdo['INTEGER32 value'].raw = 0x01020304 + pdo['BOOLEAN value'].raw = False + pdo['BOOLEAN value 2'].raw = True + + self.pdo = pdo + self.node = node - # Check expected data - self.assertEqual(map.data, b'\xfd\xff\xef\x04\x03\x02\x01\x02') + def test_pdo_map_bit_mapping(self): + self.assertEqual(self.pdo.data, b'\xfd\xff\xef\x04\x03\x02\x01\x02') - # Read values from data - self.assertEqual(map['INTEGER16 value'].raw, -3) - self.assertEqual(map['UNSIGNED8 value'].raw, 0xf) - self.assertEqual(map['INTEGER8 value'].raw, -2) - self.assertEqual(map['INTEGER32 value'].raw, 0x01020304) - self.assertEqual(map['BOOLEAN value'].raw, False) - self.assertEqual(map['BOOLEAN value 2'].raw, True) + def test_pdo_map_getitem(self): + pdo = self.pdo + self.assertEqual(pdo['INTEGER16 value'].raw, -3) + self.assertEqual(pdo['UNSIGNED8 value'].raw, 0xf) + self.assertEqual(pdo['INTEGER8 value'].raw, -2) + self.assertEqual(pdo['INTEGER32 value'].raw, 0x01020304) + self.assertEqual(pdo['BOOLEAN value'].raw, False) + self.assertEqual(pdo['BOOLEAN value 2'].raw, True) + def test_pdo_getitem(self): + node = self.node self.assertEqual(node.tpdo[1]['INTEGER16 value'].raw, -3) self.assertEqual(node.tpdo[1]['UNSIGNED8 value'].raw, 0xf) self.assertEqual(node.tpdo[1]['INTEGER8 value'].raw, -2) @@ -54,10 +59,26 @@ def test_bit_mapping(self): self.assertEqual(node.tpdo[0x2002].raw, 0xf) self.assertEqual(node.pdo[0x1600][0x2002].raw, 0xf) - def test_save_pdo(self): - node = canopen.Node(1, SAMPLE_EDS) - node.tpdo.save() - node.rpdo.save() + def test_pdo_save(self): + self.node.tpdo.save() + self.node.rpdo.save() + + def test_pdo_export(self): + import tempfile + try: + import canmatrix + except ImportError: + raise unittest.SkipTest("The PDO export API requires canmatrix") + + for pdo in "tpdo", "rpdo": + with tempfile.NamedTemporaryFile(suffix=".csv") as tmp: + fn = tmp.name + with self.subTest(filename=fn, pdo=pdo): + getattr(self.node, pdo).export(fn) + with open(fn) as csv: + header = csv.readline() + self.assertIn("ID", header) + self.assertIn("Frame Name", header) if __name__ == "__main__":