From 86bf025f4a1499cd1ef4c504501ce21baa458503 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 1/8] fix unit display name for Kelvins There are no "Kelvin degrees", it's always just Kelvins. --- 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..f0c1ea3 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -90,7 +90,7 @@ 0x23: "kV", 0x24: "kA", 0x25: "°C", - 0x26: "°K", + 0x26: "K", 0x27: "l", 0x28: "m³", 0x29: "l/h", From d5381b8a741d43161d6b246f559a477c50563c5d 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 2/8] Fix unit of plain Watts This is stated in the "58101758_B1_GB_02.2021" document (Modbus register mapping, Modbus RTU, Modbus/KMP TCP/IP"). It also makes sense, looking at all the other values in the table. --- 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 f0c1ea3..e84afc3 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -73,7 +73,7 @@ 0x12: "kVAh", 0x13: "MVAh", 0x14: "GVAh", - 0x15: "kW", + 0x15: "W", 0x16: "kW", 0x17: "MW", 0x18: "GW", From 2a1926ca9fb655d0b0da61fd7573a17923223127 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 3/8] Add missing units These data 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\d say there's a high chance of them being correct. --- src/pykmp/constants.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index e84afc3..787c340 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -117,6 +117,12 @@ 0x3E: "days", 0x3F: "RTC-Q", 0x40: "Datetime", + 0x55: "%RH", + 0x56: "%O\N{SUBSCRIPT TWO}", + 0x57: "m/s", + 0x58: "kJ/kg", + 0x59: "pH", + 0x5A: "g/kg", } From 754da5710bcc3031ef61ab8291afb1c143c03a2e 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 4/8] Fix unit of "var" 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, this is very likely a typo. --- 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 787c340..14ce4c3 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -77,7 +77,7 @@ 0x16: "kW", 0x17: "MW", 0x18: "GW", - 0x19: "kvar", + 0x19: "var", 0x1A: "kvar", 0x1B: "Mvar", 0x1C: "Gvar", From 66a16f81fd17cdb60591295f98d02957a9fc0913 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 5/8] add E1/V1 identification to relevant registers 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 14ce4c3..fe5a12a 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -17,8 +17,8 @@ # Decimals of each variable in the GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { 0x003C: "Heat Energy (E1)", - 0x0044: "Volume", - 0x004A: "Flow", + 0x0044: "Volume V1", + 0x004A: "Flow V1", 0x0050: "Current Power", 0x0056: "Temp1", 0x0057: "Temp2", From 42f1b6d2b61d5e8ef183e967a97706ed9419b10e 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 6/8] Identify the E8/E9 energy registers 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 fe5a12a..318c43a 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -23,8 +23,8 @@ 0x0056: "Temp1", 0x0057: "Temp2", 0x0059: "Tempdiff", - 0x0061: "Temp1xm3", - 0x006E: "Temp2xm3", + 0x0061: "Temp1xm3 E8", # V1 * t1 (inlet) + 0x006E: "Temp2xm3 E9", # V2 * t2 (outlet) 0x0071: "Infoevent", 0x007B: "MaxFlowDate_Y", 0x007C: "MaxFlow_Y", From faadfe3b408ea2c5044f210f07488ce6cc4afb55 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 7/8] Add a lot of registers 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 is very likely the Q_p, aka the nominal flow, - 400 is an educated guess, a one-decimal-digit precise T1-T2 difference - 675 is only there on our WM-Bus enabled Multical 303 which were procured with a transmission interval of 96 seconds I'm using decimal numbers instead of hex because that's what the vast majority of the documents use. I'll change the existing constants in a follow-up commit. --- src/pykmp/constants.py | 95 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index 318c43a..9cf4ff8 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -17,15 +17,40 @@ # Decimals of each variable in the GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { 0x003C: "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", 0x0044: "Volume V1", + 69: "Volume V2", + 72: "Mass M1", + 73: "Mass M2", 0x004A: "Flow V1", + 75: "Flow V2", 0x0050: "Current Power", + 84: "Pulse input A1", + 85: "Pulse input B1", 0x0056: "Temp1", 0x0057: "Temp2", + 88: "Temp3", 0x0059: "Tempdiff", + 91: "Pressure P1", + 92: "Pressure P1", + 94: "Heat Energy E2", + 95: "Tap water energy E6", + 96: "Tap water energy E7", 0x0061: "Temp1xm3 E8", # V1 * t1 (inlet) + 98: "Target Date", + 99: "InfoCode", + 104: "Meter number for VB", 0x006E: "Temp2xm3 E9", # V2 * t2 (outlet) + 112: "Customer Number 2", # 8 most-significant digits 0x0071: "Infoevent", + 114: "Meter number for VA", + 122: "Temp4", 0x007B: "MaxFlowDate_Y", 0x007C: "MaxFlow_Y", 0x007D: "MinFlowDate_Y", @@ -46,8 +71,78 @@ 0x0093: "AvgTemp2_Y", 0x0095: "AvgTemp1_M", 0x0096: "AvgTemp2_M", + 152: "Program number", # not seen on my 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 our Multical 603 + 184: "MbusPriAdrMod1", + 185: "MbusSekAdrMod1", + 218: "MbusPriAdrMod2", + 219: "MbusSekAdrMod2", + 222: "ConfigChangedEventCount", + 224: "Pulse input A2", + 225: "Pulse input B2", + 229: "T1_average_autoint", + 230: "T2_average_autoint", + 234: "l/imp. for VA", + 235: "l/imp for VB", + 239: "Volume V1 hires", + # Undocumented, but it matches the info given by our Multical 603 in the diagnostics display + 259: "Nominal Q\N{LATIN SUBSCRIPT SMALL LETTER P}", 0x010A: "E1HighRes", + 267: "Cooling energy E3 hires", + 346: "Module SW rev", + 347: "Customer Number", + 348: "Date and Time", # FIXME unkown 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", + 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? + 385: "Power max year date", + # 386: something similar? + 387: "Flow V1 max month date", + # 388: something similar? + 389: "Power max month date", + # 390: something similar? + 398: "T1 actual (one decimal)", + 399: "T2 actual (one decimal)", + # Undocumented, but on Multical 603 it works that way + 400: "T1-T2 (one decimal)", + 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", 0x03EC: "HourCounter", + 1005: "Software edition", + 1010: "Customer number 1", # 8 least-significant digits + 1032: "Operation Mode", } From 6873a3caecae04e691eaa52725b60bfe76742b0e 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 8/8] convert register numbers to decimal 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. I checked that the resulting content of the list is exactly the same. --- src/pykmp/constants.py | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/pykmp/constants.py b/src/pykmp/constants.py index 9cf4ff8..f31493e 100644 --- a/src/pykmp/constants.py +++ b/src/pykmp/constants.py @@ -16,7 +16,7 @@ # Decimals of each variable in the GetRegister command request/response (CID=0x10) REGISTERS: Final[Mapping[int, str]] = { - 0x003C: "Heat Energy (E1)", + 60: "Heat Energy (E1)", 61: "Inlet Energy E4", 62: "Outlet Energy E5", 63: "Cooling Energy E3", @@ -24,53 +24,53 @@ 65: "Tariff TA3", 66: "Tariff limit 2", 67: "Tariff limit 3", - 0x0044: "Volume V1", + 68: "Volume V1", 69: "Volume V2", 72: "Mass M1", 73: "Mass M2", - 0x004A: "Flow V1", + 74: "Flow V1", 75: "Flow V2", - 0x0050: "Current Power", + 80: "Current Power", 84: "Pulse input A1", 85: "Pulse input B1", - 0x0056: "Temp1", - 0x0057: "Temp2", + 86: "Temp1", + 87: "Temp2", 88: "Temp3", - 0x0059: "Tempdiff", + 89: "Tempdiff", 91: "Pressure P1", 92: "Pressure P1", 94: "Heat Energy E2", 95: "Tap water energy E6", 96: "Tap water energy E7", - 0x0061: "Temp1xm3 E8", # V1 * t1 (inlet) + 97: "Temp1xm3 E8", # V1 * t1 (inlet) 98: "Target Date", 99: "InfoCode", 104: "Meter number for VB", - 0x006E: "Temp2xm3 E9", # V2 * t2 (outlet) + 110: "Temp2xm3 E9", # V2 * t2 (outlet) 112: "Customer Number 2", # 8 most-significant digits - 0x0071: "Infoevent", + 113: "Infoevent", 114: "Meter number for VA", 122: "Temp4", - 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", + 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", 152: "Program number", # not seen on my meters: ABCCCCCC 153: "Config number 1", # config no. DDDEE 154: "Software Checksum 1", @@ -95,7 +95,7 @@ 239: "Volume V1 hires", # Undocumented, but it matches the info given by our Multical 603 in the diagnostics display 259: "Nominal Q\N{LATIN SUBSCRIPT SMALL LETTER P}", - 0x010A: "E1HighRes", + 266: "E1HighRes", 267: "Cooling energy E3 hires", 346: "Module SW rev", 347: "Customer Number", @@ -139,7 +139,7 @@ 1001: "Fabrication No", 1002: "Time", 1003: "Date", - 0x03EC: "HourCounter", + 1004: "HourCounter", 1005: "Software edition", 1010: "Customer number 1", # 8 least-significant digits 1032: "Operation Mode",