Python script to predict ecodan output based on Ecodan databook

I thought I would put this in a new topic since my last post wandered of a bit.

So I have developed a bit of python that aims to predict the heat output of an ecodan heat pump depending on flow temp , input power and ambient temp. So on the face of it it does the same job as the simulated carnot equation on the heatpump app. The reason for doing it is that there seems to be some variation in COP for a given flow and ambient - depending on presumably the speed of the compressor - I am not sure to what extent if any the carnot equation would take this into account - I assume the variation is due to losses in the compressor being more pronounced at lower throughout - but Im no fridge man so dont really know.

Any way python code to follow once I work out how to include it.

import json
import time

import numpy as np

#Ecodan output predictor - this program will predict expected output from a 14kw Mitsubishi Ecodan Heat pump based on the manufacturers data and for a given flow temp
# ambient temp and input power




def find_output_power(input_power,cop_array,output_array):

# this routine calculates the output of the heatpup  for a given input power to the heatpump - based on the given COP's and outputs from the  ecodan data sheet 
# the inputs to the routine as follows:
# input power - is the power being delivered to the heatpump
# cop_array - is an array passed to the routin which  contains the COP's for a given flow temp and ambient temp and for a range of output powers as defined in the ecodan data book
# output_array - is an array containing the corresponding outputs from the ecodan databook
# The routine uses linear interpolation to find the output power for a given inout power by interpolating between the points in the arrays.

    
#    print(cop_array)
 #   print(output_array)
    
    output_power = 0

#determine which range we are operating in - the data sheet gives a range of four outputs for the heatpump as well as corresponding COP - from this we can calculate 
# the corresponding input power = output power/cop - and find what range we are currently in from the current iput power

    if input_power > (output_array[3]/cop_array[3]) :   indexh = 3
    if input_power <= (output_array[3]/cop_array[3]) :   indexh = 3
    if input_power <=(output_array[2]/cop_array[2]) :   indexh = 2
    if input_power <= (output_array[1]/cop_array[1]) :   indexh = 1
    indexl = indexh-1
 # find the high and low boundaries of the operating region/
    
    inpowerl = output_array[indexl]/cop_array[indexl]
    inpowerh = output_array[indexh]/cop_array[indexh]
    outpowerl = output_array[indexl]
    outpowerh = output_array[indexh]
    
 #   print(inpowerl)
  #  print(inpowerh)
 #   print(outpowerl)
 #   print(outpowerh)

# calculate the slope of the line in the operating region
    slope = (outpowerh-outpowerl)/(inpowerh - inpowerl)
 #   print (slope)
 # calculate the output power   
    output_power = (slope * (input_power - inpowerl)) + outpowerl
 #   print(output_power)
    return (output_power)






    
def bilinear_interpolation(x,y,x_,y_,val):

# this routine does bilinear interpolation between two points in a 2 d plane - got it off the web

    a = 1 /((x_[1] - x_[0]) * (y_[1] - y_[0]))
    xx = np.array([[x_[1]-x],[x-x_[0]]],dtype='float32')
    f = np.array(val).reshape(2,2)
    yy = np.array([[y_[1]-y],[y-y_[0]]],dtype='float32')
    b = np.matmul(f,yy)

    return a * np.matmul(xx.T, b)   
    

##main part of the program

# run a loop to evaluate every 20 seconds
while True :  

	#get the heatpump input power from emoncms

	header = {'content-type':'application/json'}

	url = "http://192.168.0.100/input/get/emonpi/power2&apikey=00493cf9xxxxxxxxxxx"
	response = requests.get(url,headers = header)
	str_response = response.content.decode("utf-8")
	#print(str_response)
	#print(type(str_response))

	response_dict =  response.json()
	#print(type(response_dict))
	#print(response_dict)

	# set the power used for pumps etc - i.e this needs to be subtracted from power input to heat pump
	pumping_power = 190  

	input_power = ( response_dict["value"] - pumping_power)/1000	
#	print(input_power)



	#get heatpump flow temp from emoncms

	url = "http://192.168.0.100/input/get/emontxshield/t1&apikey=00493cxxxxxxxxxxxxx"
	response = requests.get(url,headers = header)
	str_response = response.content.decode("utf-8")
	#print(str_response)
	#print(type(str_response))

	response_dict =  response.json()
	#print(type(response_dict))
	#print(response_dict)
	#print(response_dict["value"]/1000)
	flow_temp =  response_dict["value"]
#	print(flow_temp)



	#get outside air temp from emoncms

	url = "http://192.168.0.100/input/get/emontxshield/t5&apikey=00493cf9xxxxxxxxxxxxxxxxx"
	response = requests.get(url,headers = header)
	str_response = response.content.decode("utf-8")
	#print(str_response)
	#print(type(str_response))



	response_dict =  response.json()
	#print(type(response_dict))
	#print(response_dict)
	#print(response_dict["value"]/1000)
	ambient_temp =  response_dict["value"]
#	print(ambient_temp)

	

	# following sets up all the data arrays for different combinations of flow and ambient temperature - based on the values from the ecodan databook 
	# if you have a different heatpump you will have to edit all of these to suit your model
	# for each point on the flow x ambient table there is an array for output and cop at 5 different invertor power levels

	f25am25_output = np.array([5.5,8,10,10])
	f25am25_cop = np.array([1.75,1.75,1.65,1.65])

	f25am20_output = np.array([6.7,8.4,10.5,12])
	f25am20_cop = np.array([2.05,2.15,2,1.75])

	f25am15_output = np.array([7.8,8.8,11,14])
	f25am15_cop = np.array([2.35,2.5,2.3,1.85])

	f25am10_output = np.array([5.9,11.2,14,14.4])
	f25am10_cop = np.array([2.3,2.7,2.5,2.25])

	f25am7_output = np.array([4,11.2,14,15.9])
	f25am7_cop = np.array([2.25,3,2.8,2.5])

	f25a2_output = np.array([5.1,11.2,14,16.3])
	f25a2_cop = np.array([3.65,3.4,3.15,3])

	f25a7_output = np.array([4.2,11.2,14,16.6])
	f25a7_cop = np.array([4.45,4.8,4.45,4.25])

	f25a12_output = np.array([6.2,11.2,14,16.8])
	f25a12_cop = np.array([4.9,5.55,5.15,4.9])

	f25a15_output = np.array([6.4,11.2,14,18.2])
	f25a15_cop = np.array([5.1,5.65,5.25,5])

	f25a20_output = np.array([6.7,11.2,14,20.8])
	f25a20_cop = np.array([8,7.35,6.85,6.5])


	f35am25_output = np.array([5.5,8,10,10])
	f35am25_cop = np.array([1.75,1.75,1.65,1.65])

	f35am20_output = np.array([6.7,8.4,10.5,12])
	f35am20_cop = np.array([2.05,2.15,2,1.75])

	f35am15_output = np.array([7.8,8.8,11,14])
	f35am15_cop = np.array([2.35,2.5,2.3,1.85])

	f35am10_output = np.array([5.9,11.2,14,14.4])
	f35am10_cop = np.array([2.3,2.7,2.5,2.25])

	f35am7_output = np.array([4,11.2,14,15.9])
	f35am7_cop = np.array([2.25,3,2.8,2.5])

	f35a2_output = np.array([5.1,11.2,14,16.3])
	f35a2_cop = np.array([3.65,3.4,3.15,3])

	f35a7_output = np.array([4.2,11.2,14,16.6])
	f35a7_cop = np.array([4.45,4.8,4.45,4.25])

	f35a12_output = np.array([6.2,11.2,14,16.8])
	f35a12_cop = np.array([4.9,5.55,5.15,4.9])

	f35a15_output = np.array([6.4,11.2,14,18.2])
	f35a15_cop = np.array([5.1,5.65,5.25,5])

	f35a20_output = np.array([6.7,11.2,14,20.8])
	f35a20_cop = np.array([8,7.35,6.85,6.5])

	f40am25_output = np.array([5,7.7,9.7,9.7])
	f40am25_cop = np.array([1.5,1.65,1.5,1.5])

	f40am20_output = np.array([6.1,8.1,10.2,11.6])
	f40am20_cop = np.array([1.75,2,1.85,1.6])

	f40am15_output = np.array([7.2,8.8,11,13.5])
	f40am15_cop = np.array([2,2.3,2.1,1.7])

	f40am10_output = np.array([5.6,11.2,14,14.2])
	f40am10_cop = np.array([2.05,2.55,2.2,2.1])

	f40am7_output = np.array([4,11.2,14,15.7])
	f40am7_cop = np.array([2.05,2.75,2.55,2.3])

	f40a2_output = np.array([4.7,11.2,14,16])
	f40a2_cop = np.array([3.1,3.15,2.9,2.75])

	f40a7_output = np.array([3.7,11.2,14,16.4])
	f40a7_cop = np.array([3.5,4.35,4,3.8])

	f40a12_output = np.array([5.8,11.2,14,16.6])
	f40a12_cop = np.array([4.65,5.25,4.8,4.55])

	f40a15_output = np.array([5.9,11.2,14,17.9])
	f40a15_cop = np.array([5.05,5.45,5,4.75])

	f40a20_output = np.array([6.4,11.2,14,20.5])
	f40a20_cop = np.array([6.65,6.6,6.05,5.75])

	f45am25_output = np.array([4.6,7.5,9.3,9.3])
	f45am25_cop = np.array([1.2,1.5,1.35,1.35])

	f45am20_output = np.array([5.5,7.8,9.8,11.2])
	f45am20_cop = np.array([1.4,1.85,1.65,1.45])

	f45am15_output = np.array([6.5,8.8,11,13.1])
	f45am15_cop = np.array([1.6,2.1,1.9,1.55])

	f45am10_output = np.array([5.3,11.2,14,14])
	f45am10_cop = np.array([1.75,2.35,1.9,1.9])

	f45am7_output = np.array([4,11.2,14,15.5])
	f45am7_cop = np.array([1.85,2.5,2.3,2.1])

	f45a2_output = np.array([4.2,11.2,14,15.8])
	f45a2_cop = np.array([2.5,2.9,2.65,2.5])

	f45a7_output = np.array([3.2,11.2,14,16.1])
	f45a7_cop = np.array([2.55,3.85,3.5,3.3])

	f45a12_output = np.array([5.4,11.2,14,16.4])
	f45a12_cop = np.array([4.35,4.9,4.45,4.2])

	f45a15_output = np.array([5.4,11.2,14,17.7])
	f45a15_cop = np.array([4.95,5.25,4.75,4.5])

	f45a20_output = np.array([6,11.2,14,20.2])
	f45a20_cop = np.array([5.3,5.8,5.25,4.95])

	f50am25_output = np.array([3.6,11.2,14,14.7])
	f50am25_cop = np.array([2.4,4.87,6.51,7.17])

	f50am20_output = np.array([0,0,0,0])
	f50am20_cop = np.array([1,1,1,1])

	f50am20_output = np.array([0,0,0,0])
	f50am20_cop = np.array([1,1,1,1])

	f50am15_output = np.array([5.7,8.8,11,12.6])
	f50am15_cop = np.array([1.3,1.9,1.75,1.45])

	f50am10_output = np.array([4.6,11.2,14,14])
	f50am10_cop = np.array([1.45,2.1,1.85,1.85])

	f50am7_output = np.array([3.6,11.2,14,14.7])
	f50am7_cop = np.array([1.5,2.3,2.15,2.05])

	f50a2_output = np.array([3.7,11.2,14,15.1])
	f50a2_cop = np.array([2.05,2.75,2.55,2.45])

	f50a7_output = np.array([2.7,11.2,14,15.4])
	f50a7_cop = np.array([2,3.4,3.15,2.9])

	f50a12_output = np.array([5.2,11.2,14,15.6])
	f50a12_cop = np.array([3.7,4.3,4,3.7])

	f50a15_output = np.array([5.3,11.2,14,16.9])
	f50a15_cop = np.array([4.35,4.65,4.3,4])

	f50a20_output = np.array([5.6,11.2,14,19.3])
	f50a20_cop = np.array([4.4,5.1,4.75,4.4])



	f55am25_output = np.array([3.1,11.2,14,14.1])
	f55am25_cop = np.array([0,0,0,0])

	f55am20_output = np.array([3.1,11.2,14,14.1])
	f55am20_cop = np.array([0,0,0,0])

	f55am15_output = np.array([4.8,8.8,11,12.2])
	f55am15_cop = np.array([1,1.75,1.6,1.3])

	f55am10_output = np.array([4,11.2,14,14])
	f55am10_cop = np.array([1.1,1.85,1.75,1.75])

	f55am7_output = np.array([3.1,11.2,14,14])
	f55am7_cop = np.array([1.15,2.1,1.95,1.95])

	f55a2_output = np.array([3.2,11.2,14,14.3])
	f55a2_cop = np.array([1.55,2.55,2.4,2.35])

	f55a7_output = np.array([2.2,11.2,14,14.6])
	f55a7_cop = np.array([1.45,2.9,2.75,2.5])

	f55a12_output = np.array([5,11.2,14,14.8])
	f55a12_cop = np.array([3.05,3.7,3.5,3.2])

	f55a15_output = np.array([5.2,11.2,14,16])
	f55a15_cop = np.array([3.7,4.05,3.85,3.5])

	f55a20_output = np.array([5.1,11.2,14,18.3])
	f55a20_cop = np.array([3.45,4.4,4.2,3.8])

	f60am25_output = np.array([3.2,11.2,14,14.3])
	f60am25_cop = np.array([2.06,4.39,5.83,6.09])

	f60am20_output = np.array([3.2,11.2,14,14.3])
	f60am20_cop = np.array([2.06,4.39,5.83,6.09])

	f60am15_output = np.array([3.2,11.2,14,14.3])
	f60am15_cop = np.array([2.06,4.39,5.83,6.09])

	f60am10_output = np.array([3.2,11.2,14,14.3])
	f60am10_cop = np.array([2.06,4.39,5.83,6.09])

	f60am7_output = np.array([3.2,11.2,14,14.3])
	f60am7_cop = np.array([2.06,4.39,5.83,6.09])

	f60a2_output = np.array([3.2,11.2,14,14.3])
	f60a2_cop = np.array([2.06,4.39,5.83,6.09])

	f60a7_output = np.array([3.2,11.2,14,14.3])
	f60a7_cop = np.array([2.06,4.39,5.83,6.09])

	f60a12_output = np.array([3.2,11.2,14,14.3])
	f60a12_cop = np.array([2.06,4.39,5.83,6.09])

	f60a15_output = np.array([3.2,11.2,14,14.3])
	f60a15_cop = np.array([2.06,4.39,5.83,6.09])

	f60a20_output = np.array([3.2,11.2,14,14.3])
	f60a20_cop = np.array([2.06,4.39,5.83,6.09])


	#flow_temp = 38.4   # for testing
	#ambient_temp = 7.5 # for testing
	#input_power = 2.217 # for testing

	# following code generates pointers to the 2d table of arrays based on actual ambient and flow temps

	ambient_index_low = 0
	ambient_index_high = 0
	flow_index_low = 0 
	flow_index_high = 0 

	if flow_temp > 55 : 
	    flow_index_low =  5
	    flow_index_high = 6
	    flow_low = 55
	    flow_high = 60

	if flow_temp <= 55 : 
	    flow_index_low = 4
	    flow_index_high = 5
	    flow_low = 50
	    flow_high = 55

	if flow_temp <= 50 : 
	    flow_index_low = 3
	    flow_index_high = 4
	    flow_low = 45
	    flow_high = 50
	     
	if flow_temp <= 45 : 
	    flow_index_low = 2
	    flow_index_high = 3
	    flow_low = 40
	    flow_high = 45
	    
	if flow_temp <= 40 : 
	    flow_index_low = 1
	    flow_index_high = 2
	    flow_low = 35
	    flow_high = 40
	    
	if flow_temp <= 35 : 
	    flow_index_low = 0
	    flow_index_high = 1
	    flow_low = 25
	    flow_high = 35
	    
	    
	if ambient_temp > 15 :
	    ambient_index_low = 8
	    ambient_index_high = 9
	    ambient_low = 15
	    ambient_high = 20
	    
	if ambient_temp <= 15 :
	    ambient_index_low = 7
	    ambient_index_high = 8
	    ambient_low = 12
	    ambient_high = 15
	    
	if ambient_temp <= 12 :
	    ambient_index_low = 6
	    ambient_index_high = 7
	    ambient_low = 7
	    ambient_high = 12
	  
	if ambient_temp <= 7 :
	    ambient_index_low = 5
	    ambient_index_high = 6
	    ambient_low = 2
	    ambient_high = 7
	    
	if ambient_temp <= 2 :
	    ambient_index_low = 4
	    ambient_index_high = 5
	    ambient_low = -7
	    ambient_high = 2
	    
	if ambient_temp <= -7 :
	    ambient_index_low = 3
	    ambient_index_high = 4
	    ambient_low = -10
	    ambient_high = -7
	    
	if ambient_temp <= -10 :
	    ambient_index_low = 2
	    ambient_index_high = 3
	    ambient_low = -15
	    ambient_high = -10
	    
	if ambient_temp <= -15 :
	    ambient_index_low = 1
	    ambient_index_high = 2
	    ambient_low = -20
	    ambient_high = -15
	    
	if ambient_temp <= -20 :
	    ambient_index_low = 0
	    ambient_index_high = 1
	    ambient_low = -25
	    ambient_high = -20

#	print(flow_index_low) 
#	print(flow_index_high)
#	print(ambient_index_low)
#	print(ambient_index_high)
#	print("")
#	print(flow_low , flow_high)
#	print(ambient_low , ambient_high)



	# variables to store the results of the four points that bound the current ambient and flow temp

	output_power_A = 0 
	output_power_B = 0 
	output_power_C = 0 
	output_power_D = 0 


# build the full 2D array containing all the 1d arrays for differnet flow and ambient temps

	datatable_input_power = np.array([[f25am25_cop,f35am25_cop,f40am25_cop,f45am25_cop,f50am25_cop,f55am25_cop,f60am25_cop],[f25am20_cop,f35am20_cop,f40am20_cop,f45am20_cop,f50am20_cop,f55am20_cop,f60am20_cop],[f25am15_cop,f35am15_cop,f40am15_cop,f45am15_cop,f50am15_cop,f55am15_cop,f60am7_cop],[f25am10_cop,f35am10_cop,f40am10_cop,f45am10_cop,f50am10_cop,f55am10_cop,f60am10_cop],[f25am7_cop,f35am7_cop,f40am7_cop,f45am7_cop,f50am7_cop,f55am7_cop,f60am7_cop],[f25a2_cop,f35a2_cop,f40a2_cop,f45a2_cop,f50a2_cop,f55a2_cop,f60a2_cop],[f25a7_cop,f35a7_cop,f40a7_cop,f45a7_cop,f50a7_cop,f55a7_cop,f60a7_cop],[f25a12_cop,f35a12_cop,f40a12_cop,f45a12_cop,f50a12_cop,f55a12_cop,f60a12_cop],[f25a15_cop,f35a15_cop,f40a15_cop,f45a15_cop,f50a15_cop,f55a15_cop,f60a15_cop],[f25a20_cop,f35a20_cop,f40a20_cop,f45a20_cop,f50a20_cop,f55a20_cop,f60a20_cop]])

	datatable_output_power = np.array([[f25am25_output,f35am25_output,f40am25_output,f45am25_output,f50am25_output,f55am25_output,f60am25_output],[f25am20_output,f35am20_output,f40am20_output,f45am20_output,f50am20_output,f55am20_output,f60am20_output],[f25am15_output,f35am15_output,f40am15_output,f45am15_output,f50am15_output,f55am15_output,f60am7_output],[f25am10_output,f35am10_output,f40am10_output,f45am10_output,f50am10_output,f55am10_output,f60am10_output],[f25am7_output,f35am7_output,f40am7_output,f45am7_output,f50am7_output,f55am7_output,f60am7_output],[f25a2_output,f35a2_output,f40a2_output,f45a2_output,f50a2_output,f55a2_output,f60a2_output],[f25a7_output,f35a7_output,f40a7_output,f45a7_output,f50a7_output,f55a7_output,f60a7_output],[f25a12_output,f35a12_output,f40a12_output,f45a12_output,f50a12_output,f55a12_output,f60a12_output],[f25a15_output,f35a15_output,f40a15_output,f45a15_output,f50a15_output,f55a15_output,f60a15_output],[f25a20_output,f35a20_output,f40a20_output,f45a20_output,f50a20_output,f55a20_output,f60a20_output]])

# find the four output powers that form a bounding box around the actual current ambient and flow temp operating condition

	output_power_A = (find_output_power(input_power,datatable_input_power[ambient_index_low,flow_index_low],datatable_output_power[ambient_index_low,flow_index_low]))
	output_power_B = (find_output_power(input_power,datatable_input_power[ambient_index_low,flow_index_high],datatable_output_power[ambient_index_low,flow_index_high]))
	output_power_C = (find_output_power(input_power,datatable_input_power[ambient_index_high,flow_index_low],datatable_output_power[ambient_index_high,flow_index_low]))
	output_power_D = (find_output_power(input_power,datatable_input_power[ambient_index_high,flow_index_high],datatable_output_power[ambient_index_high,flow_index_high]))


#	print(output_power_A)
#	print(output_power_B)
#	print(output_power_C)
#	print(output_power_D)

# Do bilinear interpolation between the 4 bounding points to find the predicted output power

	output = bilinear_interpolation(x=ambient_temp,
	                       y=flow_temp,
	                       x_=[ambient_low,ambient_high],
	                       y_=[flow_low,flow_high],
	                       val=[output_power_A,output_power_B,output_power_C,output_power_D])
#	print("")                       
#	print(output[0,0])
#	print(output[0,0]/input_power)



# send the result back to emoncms using an HTTP post

	output_heat =  output[0,0]

	if output_heat <= 0 :
		output_heat = 0

	output_heat_str = str(output_heat)

	print(output_heat_str)

	jsonheat =  "heat :"+output_heat_str

#	print(jsonheat)

	url=" "
	url="http://192.168.0.100/emoncms/input/post?node=emontx&json={value1:100,output_heat:%s}&apikey=00493cxxxxxxxxxxxxxxxxxxxxx"%output_heat_str


	header = {'content-type':'application/json'}
#	print(url)

	response =requests.get(url,headers = header)

	str_response =  response.content.decode("utf-8")




	print(str_response)

# 20 second loop
	time.sleep(20)
1 Like

Tagging @MyForest who has the same model, and may have done something similar.

I presume this for installations that don’t have a heat meter fitted?

No I don’t have a heat meter - but even if you have one it would be interesting to see what you are getting compared to what Mitsi say you should get.

Hi,

I created these expected efficiencies based on Mitsubishi’s “nominal” output for the heat pump.

In this post I’ve included a chart showing the efficiencies I observed at various ambient temperatures.

1 Like

Aren’t there at least three current 14kW models and goddess only knows how many previous ones? Stating the model number may be very helpful.

its a PUZ-HWM140VHA