From c11b9001f8bba01e3bb1fa1b1aa62052dc86bac8 Mon Sep 17 00:00:00 2001 From: adabrandt <70381530+adabrandt@users.noreply.github.com> Date: Fri, 28 Aug 2020 14:01:36 +0000 Subject: [PATCH 1/2] Add support for multical 21 --- kamstrup.py | 146 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 35 deletions(-) diff --git a/kamstrup.py b/kamstrup.py index 9f53822..91754c0 100644 --- a/kamstrup.py +++ b/kamstrup.py @@ -10,11 +10,15 @@ from __future__ import print_function -# You need pySerial +# You need pySerial import serial - import math +# TODO: +# - Fix CRC errors +# - Correctly parse multical21 INFO field + + ####################################################################### # These are the variables I have managed to identify # Submissions welcome. @@ -47,7 +51,7 @@ 0x005B: "Pressure in flow", 0x005C: "Pressure in return flow", 0x004A: "Current flow in flow", - 0x004B: "Current flow in return flow" + 0x004B: "Current flow in return flow", 0x03ff: "Power In", 0x0438: "Power p1 In", 0x0439: "Power p2 In", @@ -77,6 +81,16 @@ 0x03EC: "Operation hours counter", } +kamstrup_MC21_var = { + 0x0044: "V1", + 0x00f3: "V1 Reverse", + 0x004a: "Flow", + 0x03ec: "Hours counter", + 0x0063: "Info", + 0x012b: "Meter temperature", + 0x0124: "Water temperature", +} + ####################################################################### # Units, provided by Erik Jensen @@ -161,7 +175,7 @@ def debug_msg(self, msg): def wr(self, b): b = bytearray(b) - self.debug("Wr", b); + self.debug("Wr", b) self.ser.write(b) def rd(self): @@ -194,16 +208,20 @@ def send(self, pfx, msg): self.wr(c) def recv(self): - b = bytearray() + # Skip first response, which is repetition of initial command, + # only break on 0x0d if it comes after 0x40 + b = None while True: d = self.rd() if d == None: return None if d == 0x40: b = bytearray() - b.append(d) - if d == 0x0d: - break + if b != None: + b.append(d) + if d == 0x0d: + break + c = bytearray() i = 1; while i < len(b) - 1: @@ -217,63 +235,115 @@ def recv(self): else: c.append(b[i]) i += 1 - if crc_1021(c): - self.debug_msg("CRC error") - return c[:-2] - def readvar(self, nbr): - # I wouldn't be surprised if you can ask for more than - # one variable at the time, given that the length is - # encoded in the response. Havn't tried. - - self.send(0x80, (0x3f, 0x10, 0x01, nbr >> 8, nbr & 0xff)) + crc = crc_1021(c[:-2]) + if c[-2] != crc >> 8 or c[-1] != crc & 0xff: + self.debug_msg("CRC error") - b = self.recv() - if b == None: - return (None, None) + return c[:-2] - if b[0] != 0x3f or b[1] != 0x10: - return (None, None) + def process_response(self, nbr, data): + # Process response data - if b[2] != nbr >> 8 or b[3] != nbr & 0xff: + if data[0] != nbr >> 8 or data[1] != nbr & 0xff: + self.debug_msg("NBR error") return (None, None) - if b[4] in units: - u = units[b[4]] + if data[2] in units: + u = units[data[2]] else: u = None # Decode the mantissa x = 0 - for i in range(0,b[5]): + for i in range(0,data[3]): x <<= 8 - x |= b[i + 7] + x |= data[i + 5] # Decode the exponent - i = b[6] & 0x3f - if b[6] & 0x40: + i = data[4] & 0x3f + if data[4] & 0x40: i = -i i = math.pow(10,i) - if b[6] & 0x80: + if data[4] & 0x80: i = -i x *= i if False: # Debug print s = "" - for i in b[:4]: + for i in data[:2]: s += " %02x" % i s += " |" - for i in b[4:7]: + for i in data[2:5]: s += " %02x" % i s += " |" - for i in b[7:]: + for i in data[5:5+data[3]]: + s += " %02x" % i + s += " ||" + for i in data[5+data[3]:]: s += " %02x" % i - print(s, "=", x, units[b[4]]) + print(s, "=", x, units[data[2]]) + + return x, u + + def readvar(self, nbr): + # Read single variable + + self.send(0x80, (0x3f, 0x10, 0x01, nbr >> 8, nbr & 0xff)) + + b = self.recv() + if b == None: + return (None, None) + + if b[0] != 0x3f or b[1] != 0x10: + return (None, None) + + x, u = self.process_response(nbr, b[2:]) return (x, u) - + + def readvar_multiple(self, multiple_nbr): + # Read multiple vars at once + + # Construct request + req = bytearray() + req.append(0x3f) #destination address + req.append(0x10) #CID + req.append(len(multiple_nbr)) #number of nbrs + for nbr in multiple_nbr: + req.append(nbr >> 8) + req.append(nbr & 0xff) + + self.send(0x80, req) + + # Process response + b = self.recv() + if b == None: + return (None, None) + + # Check destination address and CID + if b[0] != 0x3f or b[1] != 0x10: + return (None, None) + + # Decode response data, containing multiple variables + result = {} + remaining_data = b[2:] + counter = 0 + # Continue processing data until all variables processed + while counter < (len(multiple_nbr)): + current_nbr = multiple_nbr[counter] + x, u = self.process_response(current_nbr,remaining_data) + result[current_nbr] = (x,u) + # length of current variable response data = + # nbr (2) + units (1) + length (1) + sigexp (1) (=5) + # + length of actual value + len_current_nbr = 5 + remaining_data[3] + remaining_data = remaining_data[len_current_nbr:] + counter += 1 + + return result if __name__ == "__main__": @@ -284,3 +354,9 @@ def readvar(self, nbr): for i in kamstrup_382_var: x,u = foo.readvar(i) print("%-25s" % kamstrup_382_var[i], x, u) + + # Multiple var example using multical 21: + # result = foo.readvar_multiple(kamstrup_MC21_var.keys()) + # for i in result: + # x, u = result[i] + # print("%-25s" % kamstrup_MC21_var[i], x, u) From 01ce1c472c08fd7d4d0e399373d9f78e66e310cb Mon Sep 17 00:00:00 2001 From: adabrandt <70381530+adabrandt@users.noreply.github.com> Date: Fri, 28 Aug 2020 15:27:43 +0000 Subject: [PATCH 2/2] Restore original crc check of response --- kamstrup.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/kamstrup.py b/kamstrup.py index 91754c0..9642050 100644 --- a/kamstrup.py +++ b/kamstrup.py @@ -14,10 +14,6 @@ import serial import math -# TODO: -# - Fix CRC errors -# - Correctly parse multical21 INFO field - ####################################################################### # These are the variables I have managed to identify @@ -235,11 +231,8 @@ def recv(self): else: c.append(b[i]) i += 1 - - crc = crc_1021(c[:-2]) - if c[-2] != crc >> 8 or c[-1] != crc & 0xff: + if crc_1021(c): self.debug_msg("CRC error") - return c[:-2] def process_response(self, nbr, data):