# Borrowed most of the code from https://github.com/bsdphk/PyKamstrup
#!/usr/local/bin/python
#
# ----------------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42):
# <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
# can do whatever you want with this stuff. If we meet some day, and you think
# this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
# ----------------------------------------------------------------------------
#

#from __future__ import print_function

# You need pySerial 
import math

import badge
import ugfx
import easydraw

ugfx.init()
ugfx.clear( ugfx.BLACK )
ugfx.flush()
ugfx.clear( ugfx.WHITE )
ugfx.flush()


#uart = UART(2, 1200)
#uart.init(1200, bits=8, parity=None, stop=1, tx=33, rx=16)

from machine import UART
irpoort = UART(2, 1200)
irpoort.init(1200, bits=8, parity=None, stop=2, tx=33, rx=16)

#######################################################################
# These are the variables I have managed to identify
# Submissions welcome.

kamstrup_382_var = {

	0x0001: "Energy in",
	0x0002: "Energy out",

	0x000d: "Energy in hi-res",
	0x000e: "Energy out hi-res",

	0x041e: "Voltage p1",
	0x041f: "Voltage p2",
	0x0420: "Voltage p3",

	0x0434: "Current p1",
	0x0435: "Current p2",
	0x0436: "Current p3",

	0x03ff: "Power In",
	0x0438: "Power p1 In",
	0x0439: "Power p2 In",
	0x043a: "Power p3 In",

	0x0400: "Power In",
	0x0540: "Power p1 Out",
	0x0541: "Power p2 Out",
	0x0542: "Power p3 Out",
}

kamstrup_681_var = {
	1:	"Date",
	60:	"Heat",
	61:	"x",
	62:	"x",
	63:	"x",
	95:	"x",
	96:	"x",
	97:	"x",
}

kamstrup_MC601_var = {
	0x003C: "Energy register 1: Heat energy",
	0x0044: "Volume register V1",
	0x0058: "Current temperature T3",
	0x03EC: "Operation hours counter",
}

#Command overview
# 0x01 - GetType
# 0x02 - GetSerialNo
# 0x09 - SetClock
# 0x10 - GetRegister
#
# Special for the Multical 601/602 maybe:
# 0xA0 - GetLogTimePresent
# 0xA1 - GetLogLastPresent
# 0xA2 - GetLogIDPresent
# 0xA3 - GetLogTimePast
# 0x9B - GetEventStatus
# 0x9C - ClearEventStatus
# 0x11 - PutRegister

# Time is formatted as a 32bit binary value
# Time 16:15:57 = 161557d = 0x027715

# GetRegister (0x10)
# max of 8 registers in 1 request
# 0x80 [destination adress]
# 0x10 [number of registers] [register 1 id] [register 2 id] .. [CRC] 0x0D
# Register ID's are 16bit register identification codes


kamstrup_MC602_var = {
    1003: "Current date (YYMMDD)",
    60: "Energy Register 1: Heat energy",
    94: "Energy Register 2: Control energy",
    63: "Energy Register 3: Cooling energy",
    61: "Energy Register 4: Inlet energy (forwarded energy)",
    62: "Energy Register 5: Outlet energy (returned energy)",
    95: "Energy Register 6: Tap water energy",
    96: "Energy Register 7: Heat energy Y",
    97: "Energy Register 8: [m^3 x T1]",
    110: "Energy Register 9: [m^3 x T2]",
    64: "Tariff register 2",
    65: "Tariff register 3",
    68: "Volume register V1",
    69: "Volume register V2",
    84: "Input register VA",
    85: "Input register VB",
    72: "Mass register V1",
    73: "Mass register V2",
    1004: "Operational hour counter",
    113: "Info-event counter",
    1002: "Current time (HHMMSS)",
    99: "Infocode register, current",
    86: "Current inlet temperature (T1)",
    87: "Current outlet temperature (T2)",
    88: "Current temperature T3",
    122: "Current temperature T4",
    89: "Current temperature difference (T1-T2)",
    91: "Pressure in inlet (P1)",
    92: "Pressure in outlet (P2)",
    74: "Current flow in inlet pipe",
    75: "Current flow in outlet pipe",
    80: "Current power calculated on basis of V1-T1-T2",
    123: "Date for max flow this year",
    124: "Max flow this year",
    125: "Date for min flow this year",
    126: "Min flow this year",
    127: "Date for max power (effect) this year",
    128: "Max power (effect) this year",
    129: "Date for min power (effect) this year",
    130: "Min power (effect) this year",  
    138: "Date for max flow this month",
    139: "Max flow this month",
    140: "Date for min flow this month",
    141: "Min flow this month",
    142: "Date for max power (effect) this month",
    143: "Max power (effect) this month",
    144: "Date for min power (effect) this month",
    145: "Min power (effect) this month",
    146: "Year-to-date average for T1",
    147: "Year-to-date average for T2",
    149: "Month-to-date average for T1",
    150: "Month-to-date average for T2",
    66: "Tariff limit 2",
    67: "Tariff limit 3",
    98: "Target date (reading date)",
    152: "Program no. ABCCCCCC",
    153: "Config no. DDDEE",
    168: "Config no. FFGGMN",
    1001: "Serial no.",
    112: "Customer number (8 most important digits)",
    1010: "Customer number (8 less important digits)",
    114: "Meter number for VA",
    104: "Meter number for VB",
    1005: "Software edition (Meter type)",
    154: "Software check sum",
    155: "High resolution energy register for testing",
    157: "ID number for top module",
    158: "ID number for bottom module",
    175: "Error hour counter",
    234: "l/imp. for VA",
    235: "l/imp. for VB",
}
  

#######################################################################
# Units, provided by Erik Jensen

units = {
	0: '', 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: 'm3', 41: 'l/h', 42: 'm3/h', 43: 'm3xC',
	44: 'ton', 45: 'ton/h', 46: 'h', 47: 'hh:mm:ss', 48: 'yy:mm:dd',
	49: 'yyyy:mm:dd', 50: 'mm:dd', 51: '', 52: 'bar', 53: 'RTC',
	54: 'ASCII', 55: 'm3 x 10', 56: 'ton x 10', 57: 'GJ x 10',
	58: 'minutes', 59: 'Bitfield', 60: 's', 61: 'ms', 62: 'days',
	63: 'RTC-Q', 64: 'Datetime'
}

#######################################################################
# Kamstrup uses the "true" CCITT CRC-16
#

def crc_1021(message):
        poly = 0x1021
        reg = 0x0000
        for byte in message:
                mask = 0x80
                while(mask > 0):
                        reg<<=1
                        if byte & mask:
                                reg |= 1
                        mask>>=1
                        if reg & 0x10000:
                                reg &= 0xffff
                                reg ^= poly
        return reg

#######################################################################
# Byte values which must be escaped before transmission
#

escapes = {
	0x06: True,
	0x0d: True,
	0x1b: True,
	0x40: True,
	0x80: True,
}

#######################################################################
# And here we go....
#
class kamstrup(object):

#	def __init__(self):
#		print("init")
        
	def wr(self, b):
		b = bytearray(b)
		irpoort.write(b)
        a = irpoort.read(1)

	def rd(self):
		a = irpoort.read(1)
		if len(a) == 0:
			return None
		b = bytearray(a)[0]
		return b

	def send(self, pfx, msg):
		b = bytearray(msg)

		b.append(0)
		b.append(0)
		c = crc_1021(b)
		b[-2] = c >> 8
		b[-1] = c & 0xff

		c = bytearray()
		c.append(pfx)
		for i in b:
			if i in escapes:
				c.append(0x1b)
				c.append(i ^ 0xff)
			else:
				c.append(i)
		c.append(0x0d)
		self.wr(c)

	def recv(self):
		b = bytearray()
		while True:
			d = self.rd()
			if d == None:
				return None
			if d == 0x40:
				b = bytearray()
			b.append(d)
			if d == 0x0d:
				break
		c = bytearray()
		i = 1;
		while i < len(b) - 1:
			if b[i] == 0x1b:
				v = b[i + 1] ^ 0xff
				if v not in escapes:
					print("Missing Escape %02x" % v)
				c.append(v)
				i += 2
			else:
				c.append(b[i])
				i += 1
		if crc_1021(c):
			print("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))

		b = self.recv()
		if b == None:
			return (None, None)

		if b[0] != 0x3f or b[1] != 0x10:
			return (None, None)

		if b[2] != nbr >> 8 or b[3] != nbr & 0xff:
			return (None, None)

		if b[4] in units:
			u = units[b[4]]
		else:
			u = None

		# Decode the mantissa
		x = 0
		for i in range(0,b[5]):
			x <<= 8
			x |= b[i + 7]

		# Decode the exponent
		i = b[6] & 0x3f
		if b[6] & 0x40:
			i = -i
		i = math.pow(10,i)
		if b[6] & 0x80:
			i = -i
		x *= i

		if False:
			# Debug print
			s = ""
			for i in b[:4]:
				s += " %02x" % i
			s += " |"
			for i in b[4:7]:
				s += " %02x" % i
			s += " |"
			for i in b[7:]:
				s += " %02x" % i

			print(s, "=", x, units[b[4]])

		return (x, u)

import time
foo = kamstrup()
for i in kamstrup_MC602_var:
	x,u = foo.readvar(i)
	print("%-50s" % kamstrup_MC602_var[i], x, u)
	easydraw.msg(kamstrup_MC602_var[i] + ' ' + str(x) + ' ' + str(u), title="Still Kamstrup Anyway")