diff --git a/.github/workflows/checkpr.yml b/.github/workflows/checkpr.yml index 7b476c0..8ccdfe8 100644 --- a/.github/workflows/checkpr.yml +++ b/.github/workflows/checkpr.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13.0-rc.3"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a48e399..dfeeba4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13.0-rc.3"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/.vscode/settings.json b/.vscode/settings.json index 23b1a51..b7f6332 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,5 @@ "editor.formatOnSave": true, "modulename": "${workspaceFolderBasename}", "distname": "${workspaceFolderBasename}", - "moduleversion": "1.2.48", + "moduleversion": "1.2.49", } \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ae3b608..2180970 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,11 @@ # pyubx2 Release Notes +### RELEASE 1.2.49 + +ENHANCEMENTS: + +1. Enhance pyubx2.config_set() exception handling - addresses #173. + ### RELEASE 1.2.48 ENHANCEMENTS: diff --git a/pyproject.toml b/pyproject.toml index 77d6a05..c3265b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "pyubx2" authors = [{ name = "semuadmin", email = "semuadmin@semuconsulting.com" }] maintainers = [{ name = "semuadmin", email = "semuadmin@semuconsulting.com" }] description = "UBX protocol parser and generator" -version = "1.2.48" +version = "1.2.49" license = { file = "LICENSE" } readme = "README.md" requires-python = ">=3.9" diff --git a/src/pyubx2/_version.py b/src/pyubx2/_version.py index b672650..edefb1a 100644 --- a/src/pyubx2/_version.py +++ b/src/pyubx2/_version.py @@ -8,4 +8,4 @@ :license: BSD 3-Clause """ -__version__ = "1.2.48" +__version__ = "1.2.49" diff --git a/src/pyubx2/ubxhelpers.py b/src/pyubx2/ubxhelpers.py index 72b6830..7ef5e9f 100644 --- a/src/pyubx2/ubxhelpers.py +++ b/src/pyubx2/ubxhelpers.py @@ -19,6 +19,7 @@ import pyubx2.ubxtypes_configdb as ubcdb import pyubx2.ubxtypes_core as ubt from pyubx2.ubxtypes_core import ( + ATTTYPE, NMEA_PROTOCOL, POLL, RTCM3_PROTOCOL, @@ -125,11 +126,13 @@ def attsiz(att: str) -> int: Helper function to return attribute size in bytes. :param str: attribute type e.g. 'U002' - :return: size of attribute in bytes + :return: size of attribute in bytes, or -1 if variable length :rtype: int """ + if att == "CH": # variable length + return -1 return int(att[1:4]) @@ -274,11 +277,22 @@ def val2bytes(val, att: str) -> bytes: """ - if att == ubt.CH: # single variable-length string (e.g. INF-NOTICE) - return val.encode("utf-8", "backslashreplace") + try: + if not isinstance(val, ATTTYPE[atttyp(att)]): + raise TypeError( + f"Attribute type {att} value {val} must be {ATTTYPE[atttyp(att)]}, not {type(val)}" + ) + except KeyError as err: + raise ube.UBXTypeError(f"Unknown attribute type {att}") from err + atts = attsiz(att) - if atttyp(att) in ("C", "X"): # byte or char + if atttyp(att) == "X": # byte valb = val + elif atttyp(att) == "C": # char + if isinstance(val, str): + valb = val.encode("utf-8", "backslashreplace") + else: # byte + valb = val elif atttyp(att) in ("E", "L", "U"): # unsigned integer valb = val.to_bytes(atts, byteorder="little", signed=False) elif atttyp(att) == "A": # array of unsigned integers @@ -289,11 +303,9 @@ def val2bytes(val, att: str) -> bytes: elif atttyp(att) == "I": # signed integer valb = val.to_bytes(atts, byteorder="little", signed=True) elif att == ubt.R4: # single precision floating point - valb = struct.pack(" object: att = "" (key, val) = cfgItem if isinstance(key, str): # if key is a string (keyname) - (key, att) = cfgname2key(key) # lookup keyID & attribute type + (kid, att) = cfgname2key(key) # lookup keyID & attribute type else: - (_, att) = cfgkey2name(key) # lookup attribute type - keyb = val2bytes(key, U4) + kid = key + (key, att) = cfgkey2name(key) # lookup attribute type + keyb = val2bytes(kid, U4) valb = val2bytes(val, att) lis = lis + keyb + valb diff --git a/src/pyubx2/ubxtypes_core.py b/src/pyubx2/ubxtypes_core.py index 343bc99..2db452f 100644 --- a/src/pyubx2/ubxtypes_core.py +++ b/src/pyubx2/ubxtypes_core.py @@ -96,6 +96,18 @@ R4 = "R004" # Float (IEEE 754) Single Precision 4 bytes R8 = "R008" # Float (IEEE 754) Double Precision 8 bytes +ATTTYPE = { + "A": type([0, 1]), + "C": (type(b"0"), type("0")), + "E": type(0), + "I": type(0), + "L": type(0), + "R": (type(0), type(0.1)), + "U": type(0), + "X": type(b"0"), +} +"""Permissible attribute types""" + # *********************************************** # THESE ARE THE UBX PROTOCOL CORE MESSAGE CLASSES # *********************************************** diff --git a/src/pyubx2/ubxtypes_poll.py b/src/pyubx2/ubxtypes_poll.py index c0c61d3..3689551 100644 --- a/src/pyubx2/ubxtypes_poll.py +++ b/src/pyubx2/ubxtypes_poll.py @@ -2,7 +2,9 @@ UBX Protocol POLL payload definitions. THESE ARE THE PAYLOAD DEFINITIONS FOR _POLL_ MESSAGES _TO_ THE RECEIVER -(e.g. query configuration; request monitoring, receiver management, logging or sensor fusion status). +(e.g. query configuration; request monitoring, receiver management, +logging or sensor fusion status). + Response payloads are defined in UBX_PAYLOADS_GET. NB: Attribute names must be unique within each message class/id diff --git a/tests/test_configdb.py b/tests/test_configdb.py index 9ad2d06..bed4726 100644 --- a/tests/test_configdb.py +++ b/tests/test_configdb.py @@ -11,7 +11,7 @@ import unittest -from pyubx2 import UBXMessage, SET, POLL +from pyubx2 import UBXMessage, SET, POLL, SET_LAYER_FLASH, TXN_NONE from pyubx2.ubxtypes_configdb import UBX_CONFIG_DATABASE from tests.configdb_baseline import UBX_CONFIG_DATABASE_BASELINE @@ -63,6 +63,16 @@ def testFill_CFGVALSET(self): # test CFG-VALSET SET constructor ) self.assertEqual(str(res), EXPECTED_RESULT) + def testGOODConfigSet(self): + EXPECTED_RESULT = "" + msg = UBXMessage.config_set( + layers=SET_LAYER_FLASH, + transaction=TXN_NONE, + cfgData=[(0x40110069, 0), (0x40110068, 0.1)], + ) + # print(msg) + self.assertEqual(str(msg), EXPECTED_RESULT) + if __name__ == "__main__": # import sys;sys.argv = ['', 'Test.testName'] diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index d223f15..a15d50a 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -28,6 +28,8 @@ VALCKSUM, ERR_LOG, ERR_RAISE, + SET_LAYER_FLASH, + TXN_NONE, ) from pyubx2.ubxhelpers import ( cfgkey2name, @@ -399,6 +401,32 @@ def testNMEABADEND(self): # test truncated NMEA file # print(f'"{parsed}",') i += 1 + def testBADConfigSet(self): # test invalid configuration database value type + with self.assertRaises(TypeError): + UBXMessage.config_set( + layers=SET_LAYER_FLASH, + transaction=TXN_NONE, + cfgData=[(0x20920006, 0)], + ) + with self.assertRaises(TypeError): + UBXMessage.config_set( + layers=SET_LAYER_FLASH, + transaction=TXN_NONE, + cfgData=[(0x209100E0, b"\x00")], + ) + with self.assertRaises(TypeError): + UBXMessage.config_set( + layers=SET_LAYER_FLASH, + transaction=TXN_NONE, + cfgData=[(0x5005002A, b"\x00")], + ) + with self.assertRaises(TypeError): + UBXMessage.config_set( + layers=SET_LAYER_FLASH, + transaction=TXN_NONE, + cfgData=[(0x40110069, 0.1), (0x40110068, "0")], + ) + if __name__ == "__main__": # import sys;sys.argv = ['', 'Test.testName'] diff --git a/tests/test_static.py b/tests/test_static.py index 4629597..55c254e 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -17,6 +17,7 @@ import pyubx2.ubxtypes_core as ubt from pyubx2 import POLL, SET, UBX_CLASSES, UBXMessage, UBXReader from pyubx2.ubxhelpers import ( + attsiz, att2idx, att2name, bytes2val, @@ -258,6 +259,10 @@ def testhextable(self): # test hextable*( method) res = hextable(b"$GNGLL,5327.04319,S,00214.41396,E,223232.00,A,A*68\r\n", 8) self.assertEqual(res, EXPECTED_RESULT) + def testattsiz(self): # test attsiz + self.assertEqual(attsiz("CH"), -1) + self.assertEqual(attsiz("C032"), 32) + def testatt2idx(self): # test att2idx EXPECTED_RESULT = [4, 16, 101, 0, (3, 6), 0] atts = ["svid_04", "gnssId_16", "cno_101", "gmsLon", "gnod_03_06", "dodgy_xx"]