From c1a72ead04b02e8819c645a1b01c299117651551 Mon Sep 17 00:00:00 2001 From: Gert van Dijk Date: Sun, 19 Apr 2026 22:20:04 +0200 Subject: [PATCH 01/11] constants: reword code comment on REGISTERS --- src/pykmp/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index f4de63a..0429ccb 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -14,7 +14,7 @@ from typing import Final # pragma: no cover -# Decimals of each variable in the GetRegister command request/response (CID=0x10) +# Register display names as used with GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { 0x003C: "Heat Energy (E1)", 0x0044: "Volume", From 2d82017f085b3467ce1401d5ad014668e54294c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sat, 24 Jan 2026 17:39:39 +0100 Subject: [PATCH 02/11] constants: use decimal IDs for registers and units instead of hex The vast majority of the vendor-provided documentation uses decimal numbers as the primary format -- and so does this tool in its default output. I only found a couple of documents which used hex numbers, and in these cases they were always accompanied with the decimal equivalent. I saw no instance of hexadecimal with no decimal equivalents. --- src/pykmp/constants.py | 194 ++++++++++++++++++++--------------------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index 0429ccb..ae93366 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -16,107 +16,107 @@ # Register display names as used with GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { - 0x003C: "Heat Energy (E1)", - 0x0044: "Volume", - 0x004A: "Flow", - 0x0050: "Current Power", - 0x0056: "Temp1", - 0x0057: "Temp2", - 0x0059: "Tempdiff", - 0x0061: "Temp1xm3", - 0x006E: "Temp2xm3", - 0x0071: "Infoevent", - 0x007B: "MaxFlowDate_Y", - 0x007C: "MaxFlow_Y", - 0x007D: "MinFlowDate_Y", - 0x007E: "MinFlow_Y", - 0x007F: "MaxPowerDate_Y", - 0x0080: "MaxPower_Y", - 0x0081: "MinPowerDate_Y", - 0x0082: "MinPower_Y", - 0x008A: "MaxFlowDate_M", - 0x008B: "MaxFlow_M", - 0x008C: "MinFlowDate_M", - 0x008D: "MinFlow_M", - 0x008E: "MaxPowerDate_M", - 0x008F: "MaxPower_M", - 0x0090: "MinPowerDate_M", - 0x0091: "MinPower_M", - 0x0092: "AvgTemp1_Y", - 0x0093: "AvgTemp2_Y", - 0x0095: "AvgTemp1_M", - 0x0096: "AvgTemp2_M", - 0x010A: "E1HighRes", - 0x03EC: "HourCounter", + 60: "Heat Energy (E1)", + 68: "Volume", + 74: "Flow", + 80: "Current Power", + 86: "Temp1", + 87: "Temp2", + 89: "Tempdiff", + 97: "Temp1xm3", + 110: "Temp2xm3", + 113: "Infoevent", + 123: "MaxFlowDate_Y", + 124: "MaxFlow_Y", + 125: "MinFlowDate_Y", + 126: "MinFlow_Y", + 127: "MaxPowerDate_Y", + 128: "MaxPower_Y", + 129: "MinPowerDate_Y", + 130: "MinPower_Y", + 138: "MaxFlowDate_M", + 139: "MaxFlow_M", + 140: "MinFlowDate_M", + 141: "MinFlow_M", + 142: "MaxPowerDate_M", + 143: "MaxPower_M", + 144: "MinPowerDate_M", + 145: "MinPower_M", + 146: "AvgTemp1_Y", + 147: "AvgTemp2_Y", + 149: "AvgTemp1_M", + 150: "AvgTemp2_M", + 266: "E1HighRes", + 1004: "HourCounter", } UNITS_NAMES: Final[Mapping[int, str]] = { - 0x00: "no unit (number)", - 0x01: "Wh", - 0x02: "kWh", - 0x03: "MWh", - 0x04: "GWh", - 0x05: "J", - 0x06: "kJ", - 0x07: "MJ", - 0x08: "GJ", - 0x09: "Cal", - 0x0A: "kCal", - 0x0B: "Mcal", - 0x0C: "Gcal", - 0x0D: "varh", - 0x0E: "kvarh", - 0x0F: "Mvarh", - 0x10: "Gvarh", - 0x11: "VAh", - 0x12: "kVAh", - 0x13: "MVAh", - 0x14: "GVAh", - 0x15: "kW", - 0x16: "kW", - 0x17: "MW", - 0x18: "GW", - 0x19: "kvar", - 0x1A: "kvar", - 0x1B: "Mvar", - 0x1C: "Gvar", - 0x1D: "VA", - 0x1E: "kVA", - 0x1F: "MVA", - 0x20: "GVA", - 0x21: "V", - 0x22: "A", - 0x23: "kV", - 0x24: "kA", - 0x25: "°C", - 0x26: "°K", - 0x27: "l", - 0x28: "m³", - 0x29: "l/h", - 0x2A: "m³/h", - 0x2B: "m³\N{MULTIPLICATION SIGN}C", - 0x2C: "ton", - 0x2D: "ton/h", - 0x2E: "h", - 0x2F: "hh:mm:ss", - 0x30: "yy:mm:dd", - 0x31: "yyyy:mm:dd", - 0x32: "mm:dd", - 0x33: "no unit (number)", - 0x34: "bar", - 0x35: "RTC", - 0x36: "ASCII", - 0x37: "m³ \N{MULTIPLICATION SIGN}10", - 0x38: "ton \N{MULTIPLICATION SIGN}10", - 0x39: "GJ \N{MULTIPLICATION SIGN}10", - 0x3A: "minutes", - 0x3B: "Bitfield", - 0x3C: "s", - 0x3D: "ms", - 0x3E: "days", - 0x3F: "RTC-Q", - 0x40: "Datetime", + 0: "no unit (number)", + 1: "Wh", + 2: "kWh", + 3: "MWh", + 4: "GWh", + 5: "J", + 6: "kJ", + 7: "MJ", + 8: "GJ", + 9: "Cal", + 10: "kCal", + 11: "Mcal", + 12: "Gcal", + 13: "varh", + 14: "kvarh", + 15: "Mvarh", + 16: "Gvarh", + 17: "VAh", + 18: "kVAh", + 19: "MVAh", + 20: "GVAh", + 21: "kW", + 22: "kW", + 23: "MW", + 24: "GW", + 25: "kvar", + 26: "kvar", + 27: "Mvar", + 28: "Gvar", + 29: "VA", + 30: "kVA", + 31: "MVA", + 32: "GVA", + 33: "V", + 34: "A", + 35: "kV", + 36: "kA", + 37: "°C", + 38: "°K", + 39: "l", + 40: "m³", + 41: "l/h", + 42: "m³/h", + 43: "m³\N{MULTIPLICATION SIGN}C", + 44: "ton", + 45: "ton/h", + 46: "h", + 47: "hh:mm:ss", + 48: "yy:mm:dd", + 49: "yyyy:mm:dd", + 50: "mm:dd", + 51: "no unit (number)", + 52: "bar", + 53: "RTC", + 54: "ASCII", + 55: "m³ \N{MULTIPLICATION SIGN}10", + 56: "ton \N{MULTIPLICATION SIGN}10", + 57: "GJ \N{MULTIPLICATION SIGN}10", + 58: "minutes", + 59: "Bitfield", + 60: "s", + 61: "ms", + 62: "days", + 63: "RTC-Q", + 64: "Datetime", } From 6706e0bf5ad84da0aed623d3aad3f8092dbd892d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 23 Jan 2026 14:31:12 +0100 Subject: [PATCH 03/11] =?UTF-8?q?constants:=20fix=20unit=20ID=2038=20displ?= =?UTF-8?q?ay=20name=20(=C2=B0K=20=E2=86=92=20K)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Kelvin degrees" is an ancient way to denote the Kelvin unit of temperature. The unit name "degree kelvin" was changed to "kelvin" in 1967 (13th CGPM, Resolution 3). See also: https://physics.nist.gov/cuu/pdf/sp330.pdf (page 34) Co-Authored-By: Gert van Dijk --- src/pykmp/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index ae93366..b101ba3 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -90,7 +90,7 @@ 35: "kV", 36: "kA", 37: "°C", - 38: "°K", + 38: "K", 39: "l", 40: "m³", 41: "l/h", From ff587273cd907ae0443b1810b208894e89aeacfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 23 Jan 2026 21:26:22 +0100 Subject: [PATCH 04/11] =?UTF-8?q?constants:=20fix=20unit=20ID=2021=20displ?= =?UTF-8?q?ay=20name=20(kW=20=E2=86=92=20W)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unit ID 21 is stated as W (watts) in the "58101758_B1_GB_02.2021" document (Modbus register mapping, Modbus RTU, Modbus/KMP TCP/IP"). It also makes sense, looking at the pattern of the order-of-magnitude scales in the table of units. The error appears to be coming from other KMP reader implementations: - https://github.com/bsdphk/PyKamstrup/commit/3ca878514a621505394c55ed951bf5c9f8a2611a - https://github.com/esphome/esphome/commit/64a47f840eecc10e463ac678e51788ecba827546#diff-244b855ce21060b07df0fb916a2d2541e6814ca7312a5dc91c05935f3e076c15R70 --- src/pykmp/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index b101ba3..4fdf3c3 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -73,7 +73,7 @@ 18: "kVAh", 19: "MVAh", 20: "GVAh", - 21: "kW", + 21: "W", 22: "kW", 23: "MW", 24: "GW", From f5b58a2f0d9f0c0991be4655e10874f38599debb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sat, 24 Jan 2026 16:25:26 +0100 Subject: [PATCH 05/11] =?UTF-8?q?constants:=20fix=20unit=20ID=2025=20displ?= =?UTF-8?q?ay=20name=20(kvar=20=E2=86=92=20var)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is speculative (I have no KMP-speaking electricity meter with an IEC optical port), but given that the "kvar" is already defined, and looking at the other units seeing a pattern of the order-of-magnitude scale, this is very likely a typo. See also the parent commit here for a similar unit scale error corrected. --- src/pykmp/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index 4fdf3c3..1b1bb8f 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -77,7 +77,7 @@ 22: "kW", 23: "MW", 24: "GW", - 25: "kvar", + 25: "var", 26: "kvar", 27: "Mvar", 28: "Gvar", From 61ab79521a35c4da71d720922918ac02c745d693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Fri, 23 Jan 2026 21:28:11 +0100 Subject: [PATCH 06/11] constants: add unit IDs 65, 66 and 85-90 IDs 85-90 come from the "58101758_B1_GB_02.2021" document (Modbus register mapping, Modbus RTU, Modbus/KMP TCP/IP"). I have not seen them in my meters, but given that all the other values match, I would say there is a good chance that they are correct. --- src/pykmp/constants.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index 1b1bb8f..29e95f6 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -117,6 +117,14 @@ 62: "days", 63: "RTC-Q", 64: "Datetime", + 65: "imp/l", + 66: "l/imp", + 85: "%RH", + 86: "%O\N{SUBSCRIPT TWO}", + 87: "m/s", + 88: "kJ/kg", + 89: "pH", + 90: "g/kg", } From a0bd37f2262e59648f5444abe70bcf34202e3fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sat, 24 Jan 2026 16:32:15 +0100 Subject: [PATCH 07/11] constants: add V1 suffix to display names of register IDs 68 and 74 Some of these calculators support multiple flow meters, so let's identify which "channel" this is using. --- src/pykmp/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index 29e95f6..7d5f586 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -17,8 +17,8 @@ # Register display names as used with GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { 60: "Heat Energy (E1)", - 68: "Volume", - 74: "Flow", + 68: "Volume V1", + 74: "Flow V1", 80: "Current Power", 86: "Temp1", 87: "Temp2", From 7c947255ffd9f041c04f9af37d4f64f3d89d194f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sat, 24 Jan 2026 16:34:05 +0100 Subject: [PATCH 08/11] constants: add E8/E9 suffixes to display names of register IDs 97 and 110 The datasheet has this information, so let's include it in the source code. --- src/pykmp/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index 7d5f586..c4a7107 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -23,8 +23,8 @@ 86: "Temp1", 87: "Temp2", 89: "Tempdiff", - 97: "Temp1xm3", - 110: "Temp2xm3", + 97: "Temp1xm3 E8", # V1 * t1 (inlet) + 110: "Temp2xm3 E9", # V2 * t2 (outlet) 113: "Infoevent", 123: "MaxFlowDate_Y", 124: "MaxFlow_Y", From 6af91e594a18c5fed88702199a8bd0b77ac19278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Sat, 24 Jan 2026 17:29:11 +0100 Subject: [PATCH 09/11] constants: add 90 new register IDs for Multical 603 and Multical 303 Almost all of these were cross-checked against a number of PDFs which I found on the web. The exceptions are documented in the code: - 259 & 260 are very likely the Q_p, aka the nominal flow, for V1 and V2 respectively. - 675 is only present on our wireless M-Bus enabled Multical 303 which were procured with a transmission interval of 96 seconds that seems to match the register value. --- src/pykmp/constants.py | 98 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index c4a7107..d325a8c 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -17,15 +17,40 @@ # Register display names as used with GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { 60: "Heat Energy (E1)", + 61: "Inlet Energy E4", + 62: "Outlet Energy E5", + 63: "Cooling Energy E3", + 64: "Tariff TA2", + 65: "Tariff TA3", + 66: "Tariff limit 2", + 67: "Tariff limit 3", 68: "Volume V1", + 69: "Volume V2", + 72: "Mass M1", + 73: "Mass M2", 74: "Flow V1", + 75: "Flow V2", 80: "Current Power", + 84: "Pulse input A1", + 85: "Pulse input B1", 86: "Temp1", 87: "Temp2", + 88: "Temp3", 89: "Tempdiff", + 91: "Pressure P1", + 92: "Pressure P2", + 94: "Heat Energy E2", + 95: "Tap water energy E6", + 96: "Tap water energy E7", 97: "Temp1xm3 E8", # V1 * t1 (inlet) + 98: "Target Date", + 99: "InfoCode", + 104: "Meter number for VB", 110: "Temp2xm3 E9", # V2 * t2 (outlet) + 112: "Customer number 2", # 8 most-significant digits 113: "Infoevent", + 114: "Meter number for VA", + 122: "Temp4", 123: "MaxFlowDate_Y", 124: "MaxFlow_Y", 125: "MinFlowDate_Y", @@ -46,8 +71,81 @@ 147: "AvgTemp2_Y", 149: "AvgTemp1_M", 150: "AvgTemp2_M", + 152: "Program number", # not seen on meters: ABCCCCCC + 153: "Config number 1", # config no. DDDEE + 154: "Software Checksum 1", + 168: "Config number 2", # config no. FFGGMN + 175: "Error hour counter", + 178: "Differential energy dE", + 179: "Control energy cE", + 180: "Differential volume dV", + 181: "Control volume cV", + # 183: looks like a meter S/N on Multical 603 + 184: "MbusPriAdrMod1", + 185: "MbusSekAdrMod1", + 218: "MbusPriAdrMod2", + 219: "MbusSekAdrMod2", + 222: "ConfigChangedEventCount", + 224: "Pulse input A2", + 225: "Pulse input B2", + 228: "Config number 3", + 229: "T1_average_autoint", + 230: "T2_average_autoint", + 234: "l/imp for VA", + 235: "l/imp for VB", + 239: "Volume V1 hires", + # Undocumented (259 and 260), but it matches the info given by Multical 603 in the + # diagnostics display + 259: "Nominal Q\N{LATIN SUBSCRIPT SMALL LETTER P} V1", + 260: "Nominal Q\N{LATIN SUBSCRIPT SMALL LETTER P} V2", 266: "E1HighRes", + 267: "Cooling energy E3 hires", + 346: "Module SW rev", + 347: "Customer number", + 348: "Date and Time", # TODO: unknown unit 79, 28591984415535 + 355: "COP Year", + 362: "Tariff TA4", + 364: "Heat energy A1", # Heat energy with discount A1, t2 < t5 limit + 365: "Heat energy A2", # Heat energy with surcharge A2, t2 > t5 limit + 366: "T5 limit", + 367: "COP Month", + 368: "Config number 4", + 369: "Info bits", + 371: "COP", + 372: "Power input B1", + 379: "T1 time average day", + 380: "T2 time average day", + 381: "T1 time average hour", + 382: "T2 time average hour", + 383: "Flow V1 max year date", + # 384: something similar as 383? + 385: "Power max year date", + # 386: something similar as 385? + 387: "Flow V1 max month date", + # 388: something similar as 387? + 389: "Power max month date", + # 390: something similar as 389? + 398: "T1 actual (one decimal)", + 399: "T2 actual (one decimal)", + 400: "T1-T2 (one decimal)", # Undocumented, but appears as such on Multical 603 + 404: "Meter Type", + 473: "Energy E10", + 474: "Energy E11", + 477: "T3 time average day", + 478: "T3 time average hour", + 505: "P1 average day", + 506: "P2 average day", + 507: "P1 average hour", + 508: "P2 average hour", + # Undocumented, but it matches the actual interval on Multical 303 + 675: "wM-Bus transmission interval", + 1001: "Fabrication No", + 1002: "Time", + 1003: "Date", 1004: "HourCounter", + 1005: "Software edition", + 1010: "Customer number 1", # 8 least-significant digits + 1032: "Operation Mode", } From 96902422e7e477e2e25e699ec824ccdf320b5936 Mon Sep 17 00:00:00 2001 From: Gert van Dijk Date: Wed, 22 Apr 2026 00:04:19 +0200 Subject: [PATCH 10/11] constants: add 18 new registers used in electricity meters These were provided by Benny Lyne Amorsen and cross-checked against Poul-Henning Kamp's bsdphk/PyKamstrup repository: https://github.com/bsdphk/PyKamstrup/blob/f5692d29180024d9a59aa35a075f380ca8018276/kamstrup.py References: gertvdijk/PyKMP#2 --- src/pykmp/constants.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index d325a8c..eb0955b 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -16,6 +16,10 @@ # Register display names as used with GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { + 1: "Energy in", + 2: "Energy out", + 13: "Energy in hires", + 14: "Energy out hires", 60: "Heat Energy (E1)", 61: "Inlet Energy E4", 62: "Outlet Energy E5", @@ -145,7 +149,21 @@ 1004: "HourCounter", 1005: "Software edition", 1010: "Customer number 1", # 8 least-significant digits + 1023: "Power in", + 1024: "Power out", 1032: "Operation Mode", + 1054: "Voltage L1", + 1055: "Voltage L2", + 1056: "Voltage L3", + 1076: "Current L1", + 1077: "Current L2", + 1078: "Current L3", + 1080: "Power in L1", + 1081: "Power in L2", + 1082: "Power in L3", + 1344: "Power out L1", + 1345: "Power out L2", + 1346: "Power out L3", } From 6e77910378df507ffdf28f2e8d40b11b32e0e59a Mon Sep 17 00:00:00 2001 From: Gert van Dijk Date: Tue, 21 Apr 2026 23:40:17 +0200 Subject: [PATCH 11/11] docs: add changelog entries for recent register and unit catalog updates --- docs/changelog.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index b42edf1..f2b8bd6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -28,6 +28,26 @@ _In development._ Thanks to [Jan Kundrát](https://github.com/jktjkt) for reporting the issue along with a proposed fix ([PR #6](https://github.com/gertvdijk/PyKMP/pull/6)). +- The catalog of units and registers in [`pykmp.constants`][pykmp.constants] has been + expanded substantially: + + - The [register display names][pykmp.constants.REGISTERS] catalog has been expanded + for an **additional 108 register IDs**. 90 of which were found on MULTICAL® 603 + and MULTICAL® 303 meters and 18 on the 382 electricity meter. + - Eight new unit IDs have been added to the + [unit display names mapping][pykmp.constants.UNITS_NAMES]. + - Several unit display names have been corrected and extended, including fixes for + Kelvin and for watt- and var-based units. + - A few register display names have been refined with clearer suffixes to denote the + 'channel'. + + Thanks to [Jan Kundrát](https://github.com/jktjkt) for contributing the extensive + list of the MULTICAL® heat meter additions along with a proposed change + ([PR #4](https://github.com/gertvdijk/PyKMP/pull/4)). + + Thanks to [Benny Lyne Amorsen](https://github.com/amorsen) for listing the registers + on the electricity meter. + ### Tooling changes - Project documentation has been migrated from [MkDocs](https://www.mkdocs.org/) with @@ -37,6 +57,10 @@ _In development._ ### Development changes +- The [`constants`][pykmp.constants] module now uses decimal IDs for + [`REGISTERS`][pykmp.constants.REGISTERS] and + [`UNITS_NAMES`][pykmp.constants.UNITS_NAMES], matching the primary numbering used in + vendor documentation and the tool's default output more closely. - Pyright has been adopted as an additional strict type checker in `run-all-linters`, with configuration aligned more closely with the VS Code IDE experience. - Binary-heavy tests and related constants have been reformatted to use