1 #include "stdafx.h"
2 #include "EnphaseAPI.h"
3 #include "../main/Helper.h"
4 #include "../main/Logger.h"
5 #include "../httpclient/UrlEncode.h"
6 #include "hardwaretypes.h"
7 #include "../main/localtime_r.h"
8 #include "../httpclient/HTTPClient.h"
9 #include "../main/json_helper.h"
10 #include "../main/RFXtrx.h"
11 #include "../main/mainworker.h"
12 #include <iostream>
13 
14 //https://enphase.com/en-us/products-and-services/envoy
15 
16 #define Enphase_request_INTERVAL 30
17 
18 #ifdef _DEBUG
19 //#define DEBUG_EnphaseAPI_R
20 //#define DEBUG_EnphaseAPI_W
21 #endif
22 
23 /*
24 Example
25 {"production":[{"type":"inverters","activeCount":9,"readingTime":1568991780,"wNow":712,"whLifetime":1448651},{"type":"eim","activeCount":1,"measurementType":"production","readingTime":1568991966,"wNow":624.315,"whLifetime":1455843.527,"varhLeadLifetime":0.001,"varhLagLifetime":311039.158,"vahLifetime":1619431.681,"rmsCurrent":2.803,"rmsVoltage":233.289,"reactPwr":137.092,"apprntPwr":654.245,"pwrFactor":0.95,"whToday":4295.527,"whLastSevenDays":74561.527,"vahToday":5854.681,"varhLeadToday":0.001,"varhLagToday":2350.158}],"consumption":[{"type":"eim","activeCount":1,"measurementType":"total-consumption","readingTime":1568991966,"wNow":1260.785,"whLifetime":2743860.336,"varhLeadLifetime":132372.858,"varhLagLifetime":273043.125,"vahLifetime":3033001.948,"rmsCurrent":5.995,"rmsVoltage":233.464,"reactPwr":437.269,"apprntPwr":1399.886,"pwrFactor":0.9,"whToday":11109.336,"whLastSevenDays":129007.336,"vahToday":13323.948,"varhLeadToday":895.858,"varhLagToday":3700.125},{"type":"eim","activeCount":1,"measurementType":"net-consumption","readingTime":1568991966,"wNow":636.47,"whLifetime":0.0,"varhLeadLifetime":132372.857,"varhLagLifetime":-37996.033,"vahLifetime":3033001.948,"rmsCurrent":3.191,"rmsVoltage":233.376,"reactPwr":574.361,"apprntPwr":744.807,"pwrFactor":0.85,"whToday":0,"whLastSevenDays":0,"vahToday":0,"varhLeadToday":0,"varhLagToday":0}],"storage":[{"type":"acb","activeCount":0,"readingTime":0,"wNow":0,"whNow":0,"state":"idle"}]}
26 */
27 
28 #ifdef DEBUG_EnphaseAPI_W
SaveString2Disk(std::string str,std::string filename)29 void SaveString2Disk(std::string str, std::string filename)
30 {
31 	FILE* fOut = fopen(filename.c_str(), "wb+");
32 	if (fOut)
33 	{
34 		fwrite(str.c_str(), 1, str.size(), fOut);
35 		fclose(fOut);
36 	}
37 }
38 #endif
39 
40 #ifdef DEBUG_EnphaseAPI_R
ReadFile(std::string filename)41 std::string ReadFile(std::string filename)
42 {
43 	std::ifstream file;
44 	std::string sResult = "";
45 	file.open(filename.c_str());
46 	if (!file.is_open())
47 		return "";
48 	std::string sLine;
49 	while (!file.eof())
50 	{
51 		getline(file, sLine);
52 		sResult += sLine;
53 	}
54 	file.close();
55 	return sResult;
56 }
57 #endif
58 
EnphaseAPI(const int ID,const std::string & IPAddress,const unsigned short)59 EnphaseAPI::EnphaseAPI(const int ID, const std::string &IPAddress, const unsigned short /*usIPPort*/) :
60 	m_szIPAddress(IPAddress)
61 {
62 	m_p1power.ID = 1;
63 	m_c1power.ID = 2;
64 	m_c2power.ID = 3;
65 
66 	m_HwdID = ID;
67 }
68 
~EnphaseAPI(void)69 EnphaseAPI::~EnphaseAPI(void)
70 {
71 }
72 
StartHardware()73 bool EnphaseAPI::StartHardware()
74 {
75 	RequestStart();
76 
77 	//Start worker thread
78 	m_thread = std::make_shared<std::thread>(&EnphaseAPI::Do_Work, this);
79 	SetThreadNameInt(m_thread->native_handle());
80 	m_bIsStarted = true;
81 	sOnConnected(this);
82 	return (m_thread != nullptr);
83 }
84 
StopHardware()85 bool EnphaseAPI::StopHardware()
86 {
87 	if (m_thread)
88 	{
89 		RequestStop();
90 		m_thread->join();
91 		m_thread.reset();
92 	}
93 	m_bIsStarted = false;
94 	return true;
95 }
96 
Do_Work()97 void EnphaseAPI::Do_Work()
98 {
99 	_log.Log(LOG_STATUS, "EnphaseAPI Worker started...");
100 	int sec_counter = Enphase_request_INTERVAL - 5;
101 
102 	while (!IsStopRequested(1000))
103 	{
104 		sec_counter++;
105 
106 		if (sec_counter % 12 == 0) {
107 			m_LastHeartbeat = mytime(NULL);
108 		}
109 
110 		if (sec_counter % Enphase_request_INTERVAL == 0)
111 		{
112 			Json::Value result;
113 			if (getProductionDetails(result))
114 			{
115 				parseProduction(result);
116 				parseConsumption(result);
117 				parseNetConsumption(result);
118 			}
119 		}
120 	}
121 	_log.Log(LOG_STATUS, "EnphaseAPI Worker stopped...");
122 }
123 
WriteToHardware(const char *,const unsigned char)124 bool EnphaseAPI::WriteToHardware(const char* /*pdata*/, const unsigned char /*length*/)
125 {
126 	return false;
127 }
128 
getSunRiseSunSetMinutes(const bool bGetSunRise)129 int EnphaseAPI::getSunRiseSunSetMinutes(const bool bGetSunRise)
130 {
131 	std::vector<std::string> strarray;
132 	std::vector<std::string> sunRisearray;
133 	std::vector<std::string> sunSetarray;
134 
135 	if (!m_mainworker.m_LastSunriseSet.empty())
136 	{
137 		StringSplit(m_mainworker.m_LastSunriseSet, ";", strarray);
138 		if (strarray.size() < 2)
139 			return false;
140 		StringSplit(strarray[0], ":", sunRisearray);
141 		StringSplit(strarray[1], ":", sunSetarray);
142 
143 		int sunRiseInMinutes = (atoi(sunRisearray[0].c_str()) * 60) + atoi(sunRisearray[1].c_str());
144 		int sunSetInMinutes = (atoi(sunSetarray[0].c_str()) * 60) + atoi(sunSetarray[1].c_str());
145 
146 		if (bGetSunRise) {
147 			return sunRiseInMinutes;
148 		}
149 		else {
150 			return sunSetInMinutes;
151 		}
152 	}
153 	return 0;
154 }
155 
getProductionDetails(Json::Value & result)156 bool EnphaseAPI::getProductionDetails(Json::Value& result)
157 {
158 	std::string sResult;
159 
160 #ifdef DEBUG_EnphaseAPI_R
161 	sResult = ReadFile("E:\\EnphaseAPI_production.json");
162 #else
163 	std::stringstream sURL;
164 	sURL << "http://" << m_szIPAddress << "/production.json";
165 
166 	if (!HTTPClient::GET(sURL.str(), sResult))
167 	{
168 		_log.Log(LOG_ERROR, "EnphaseAPI: Error getting http data!");
169 		return false;
170 	}
171 #ifdef DEBUG_EnphaseAPI_W
172 	SaveString2Disk(sResult, "E:\\EnphaseAPI_production.json")
173 #endif
174 #endif
175 
176 	bool ret = ParseJSon(sResult, result);
177 	if ((!ret) || (!result.isObject()))
178 	{
179 		_log.Log(LOG_ERROR, "EnphaseAPI: Invalid data received!");
180 		return false;
181 	}
182 	if (
183 		(result["consumption"].empty())
184 		&& (result["production"].empty())
185 		)
186 	{
187 		_log.Log(LOG_ERROR, "EnphaseAPI: Invalid (no) data received");
188 		return false;
189 	}
190 	return true;
191 }
192 
parseProduction(const Json::Value & root)193 void EnphaseAPI::parseProduction(const Json::Value& root)
194 {
195 	time_t atime = mytime(NULL);
196 	struct tm ltime;
197 	localtime_r(&atime, &ltime);
198 
199 	int ActHourMin = (ltime.tm_hour * 60) + ltime.tm_min;
200 
201 	int sunRise = getSunRiseSunSetMinutes(true);
202 	int sunSet = getSunRiseSunSetMinutes(false);
203 
204 	if (sunRise != 0 && sunSet != 0)
205 	{
206 		//We only poll one hour before sunrise till one hour after sunset
207 
208 		//GizMoCuz: why is this as the production.json is retreived anyway ?
209 
210 		if (ActHourMin + 60 < sunRise)
211 			return;
212 		if (ActHourMin - 60 > sunSet)
213 			return;
214 	}
215 
216 	if (root["production"].empty() == true)
217 	{
218 		//No production details available
219 		return;
220 	}
221 
222 	Json::Value reading = root["production"][0];
223 
224 	int musage = reading["wNow"].asInt();
225 	int mtotal = reading["whLifetime"].asInt();
226 
227 	SendKwhMeter(m_HwdID, 1, 255, musage, mtotal / 1000.0, "Enphase kWh Production");
228 
229 	m_p1power.powerusage1 = mtotal;
230 	m_p1power.powerusage2 = 0;
231 	m_p1power.usagecurrent = musage;
232 	sDecodeRXMessage(this, (const unsigned char *)&m_p1power, "Enphase Production kWh Total", 255);
233 }
234 
parseConsumption(const Json::Value & root)235 void EnphaseAPI::parseConsumption(const Json::Value& root)
236 {
237 	if (root["consumption"].empty())
238 	{
239 		return;
240 	}
241 	if (root["consumption"][0].empty())
242 	{
243 		_log.Log(LOG_ERROR, "EnphaseAPI: Invalid data received");
244 		return;
245 	}
246 
247 	Json::Value reading = root["consumption"][0];
248 
249 	int musage = reading["wNow"].asInt();
250 	int mtotal = reading["whLifetime"].asInt();
251 
252 	SendKwhMeter(m_HwdID, 2, 255, musage, mtotal / 1000.0, "Enphase kWh Consumption");
253 
254 	m_c1power.powerusage1 = mtotal;
255 	m_c1power.powerusage2 = 0;
256 	m_c1power.usagecurrent = musage;
257 	sDecodeRXMessage(this, (const unsigned char *)&m_c1power, "Enphase Consumption kWh Total", 255);
258 }
259 
parseNetConsumption(const Json::Value & root)260 void EnphaseAPI::parseNetConsumption(const Json::Value& root)
261 {
262 	if (root["consumption"].empty() == true)
263 	{
264 		return;
265 	}
266 	if (root["consumption"][1].empty() == true)
267 	{
268 		_log.Log(LOG_ERROR, "EnphaseAPI: Invalid data received");
269 		return;
270 	}
271 
272 	Json::Value reading = root["consumption"][1];
273 
274 	int musage = reading["wNow"].asInt();
275 	int mtotal = reading["whLifetime"].asInt();
276 
277 	SendKwhMeter(m_HwdID, 3, 255, musage, mtotal / 1000.0, "Enphase kWh Net Consumption");
278 
279 	m_c2power.powerusage1 = mtotal;
280 	m_c2power.powerusage2 = 0;
281 	m_c2power.usagecurrent = musage;
282 	sDecodeRXMessage(this, (const unsigned char *)&m_c2power, "Enphase Net Consumption kWh Total", 255);
283 }
284