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, <ime);
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