1 #include "stdafx.h"
2 #include "SolarMaxTCP.h"
3 #include "../main/Logger.h"
4 #include "../main/Helper.h"
5 #include <iostream>
6 #include "../main/localtime_r.h"
7 #include "../main/mainworker.h"
8 
9 #include "../main/SQLHelper.h"
10 #include "../hardware/hardwaretypes.h"
11 
12 #include <sstream>
13 
14 #define RETRY_DELAY 30
15 #define POLL_INTERVAL 30
16 
17 #define SM_DEST_ADDRESS 0x01
18 
SolarMaxTCP(const int ID,const std::string & IPAddress,const unsigned short usIPPort)19 SolarMaxTCP::SolarMaxTCP(const int ID, const std::string &IPAddress, const unsigned short usIPPort) :
20 m_szIPAddress(IPAddress)
21 {
22 	m_HwdID=ID;
23 	m_bDoRestart=false;
24 	m_usIPPort=usIPPort;
25 	m_socket = INVALID_SOCKET;
26 	m_bufferpos = 0;
27 	m_retrycntr = RETRY_DELAY;
28 }
29 
SolarMaxCalcChecksum(const unsigned char * pData,const int Len)30 static int SolarMaxCalcChecksum(const unsigned char *pData, const int Len)
31 {
32 	int chksum = 0;
33 	for (int ii = 0; ii < Len; ii++)
34 	{
35 		chksum += pData[ii];
36 		chksum %= 0xFFFF;
37 	}
38 	return chksum;
39 }
40 
MakeRequestString()41 std::string SolarMaxTCP::MakeRequestString()
42 {
43 	int SourceAddress = 0xFB;
44 	int DestAddress = SM_DEST_ADDRESS;
45 	std::string RequestString = "64:KDY;KT0;PAC;UDC;UL1;IDC;IL1;PIN;PRL;TNF;TKK";
46 	char szSendTemp[100];
47 	char szSendRequest[120];
48 	sprintf(szSendTemp, "%02X;%02X;%02X|%s|", SourceAddress, DestAddress, (unsigned int)(RequestString.size() + 16), RequestString.c_str());
49 	int Chksum = SolarMaxCalcChecksum((const unsigned char*)&szSendTemp, (int)strlen(szSendTemp));
50 	sprintf(szSendRequest, "{%s%04X}", szSendTemp, Chksum);
51 	return std::string(szSendRequest);
52 }
53 
~SolarMaxTCP(void)54 SolarMaxTCP::~SolarMaxTCP(void)
55 {
56 }
57 
StartHardware()58 bool SolarMaxTCP::StartHardware()
59 {
60 	RequestStart();
61 
62 	m_bIsStarted = true;
63 
64 	memset(&m_addr, 0, sizeof(sockaddr_in));
65 	m_addr.sin_family = AF_INET;
66 	m_addr.sin_port = htons(m_usIPPort);
67 
68 	unsigned long ip;
69 	ip = inet_addr(m_szIPAddress.c_str());
70 
71 	// if we have a error in the ip, it means we have entered a string
72 	if (ip != INADDR_NONE)
73 	{
74 		m_addr.sin_addr.s_addr = ip;
75 	}
76 	else
77 	{
78 		// change Hostname in serveraddr
79 		hostent *he = gethostbyname(m_szIPAddress.c_str());
80 		if (he == NULL)
81 		{
82 			return false;
83 		}
84 		else
85 		{
86 			memcpy(&(m_addr.sin_addr), he->h_addr_list[0], 4);
87 		}
88 	}
89 
90 	char szIP[20];
91 	unsigned char *pAddress = (unsigned char *)&m_addr.sin_addr;
92 	sprintf(szIP, "%d.%d.%d.%d", (int)pAddress[0], (int)pAddress[1], (int)pAddress[2], (int)pAddress[3]);
93 	m_endpoint = szIP;
94 
95 	m_retrycntr = RETRY_DELAY; //will force reconnect first thing
96 
97 	//Start worker thread
98 	m_thread = std::make_shared<std::thread>(&SolarMaxTCP::Do_Work, this);
99 	SetThreadNameInt(m_thread->native_handle());
100 
101 	return (m_thread != nullptr);
102 }
103 
StopHardware()104 bool SolarMaxTCP::StopHardware()
105 {
106 	if (m_thread)
107 	{
108 		RequestStop();
109 		m_thread->join();
110 		m_thread.reset();
111 	}
112 	m_bIsStarted = false;
113 	return true;
114 }
115 
ConnectInternal()116 bool SolarMaxTCP::ConnectInternal()
117 {
118 	m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
119 	if (m_socket == INVALID_SOCKET)
120 	{
121 		_log.Log(LOG_ERROR, "SolarMax: TCP could not create a TCP/IP socket!");
122 		return false;
123 	}
124 	/*
125 	//Set socket timeout to 2 minutes
126 	#if !defined WIN32
127 	struct timeval tv;
128 	tv.tv_sec = 120;
129 	setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO,(struct timeval *)&tv,sizeof(struct timeval));
130 	#else
131 	unsigned long nTimeout = 120*1000;
132 	setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&nTimeout, sizeof(DWORD));
133 	#endif
134 	*/
135 	// connect to the server
136 	int nRet;
137 	nRet = connect(m_socket, (const sockaddr*)&m_addr, sizeof(m_addr));
138 	if (nRet == SOCKET_ERROR)
139 	{
140 		closesocket(m_socket);
141 		m_socket = INVALID_SOCKET;
142 		_log.Log(LOG_ERROR, "SolarMax: TCP could not connect to: %s:%d", m_szIPAddress.c_str(), m_usIPPort);
143 		return false;
144 	}
145 
146 	_log.Log(LOG_STATUS, "SolarMax: TCP connected to: %s:%d", m_szIPAddress.c_str(), m_usIPPort);
147 
148 	sOnConnected(this);
149 	return true;
150 }
151 
disconnect()152 void SolarMaxTCP::disconnect()
153 {
154 	if (m_socket != INVALID_SOCKET)
155 	{
156 		closesocket(m_socket);
157 		m_socket = INVALID_SOCKET;
158 	}
159 }
160 
Do_Work()161 void SolarMaxTCP::Do_Work()
162 {
163 	char buf[1024];
164 	bool bFirstTime = true;
165 	int sec_counter = POLL_INTERVAL-5;
166 	while (!IsStopRequested(1000))
167 	{
168 		sec_counter++;
169 
170 		if (sec_counter % 12 == 0) {
171 			m_LastHeartbeat=mytime(NULL);
172 		}
173 
174 		if (m_socket == INVALID_SOCKET)
175 		{
176 			m_retrycntr++;
177 			if (m_retrycntr >= RETRY_DELAY)
178 			{
179 				m_retrycntr = 0;
180 				if (!ConnectInternal())
181 				{
182 					_log.Log(LOG_STATUS, "SolarMax: retrying in %d seconds...", RETRY_DELAY);
183 				}
184 			}
185 		}
186 		else
187 		{
188 			if ((sec_counter % POLL_INTERVAL == 0) || (bFirstTime))
189 			{
190 				bFirstTime = false;
191 
192 				//Request data from inverter
193 				std::string reqString = MakeRequestString();
194 				write(reqString.c_str(), reqString.size());
195 
196 				//this could take a long time... maybe there will be no data received at all,
197 				//so it's no good to-do the heartbeat timing here
198 
199 				int bread = recv(m_socket, (char*)&buf, sizeof(buf), 0);
200 				if (IsStopRequested(0))
201 					break;
202 				if (bread <= 0) {
203 					_log.Log(LOG_ERROR, "SolarMax: TCP/IP connection closed! retrying in %d seconds...", RETRY_DELAY);
204 					disconnect();
205 					m_retrycntr = 0;
206 					continue;
207 				}
208 				else
209 				{
210 					ParseData((const unsigned char *)&buf, bread);
211 				}
212 			}
213 		}
214 
215 	}
216 	disconnect();
217 
218 	_log.Log(LOG_STATUS, "SolarMax: TCP/IP Worker stopped...");
219 }
220 
write(const char * data,size_t size)221 void SolarMaxTCP::write(const char *data, size_t size)
222 {
223 	if (m_socket == INVALID_SOCKET)
224 		return; //not connected!
225 	send(m_socket, data, size, 0);
226 }
227 
WriteToHardware(const char * pdata,const unsigned char length)228 bool SolarMaxTCP::WriteToHardware(const char *pdata, const unsigned char length)
229 {
230 	if (isConnected())
231 	{
232 		write(pdata, length);
233 		return true;
234 	}
235 	return false;
236 }
237 
ParseData(const unsigned char * pData,int Len)238 void SolarMaxTCP::ParseData(const unsigned char *pData, int Len)
239 {
240 	int ii = 0;
241 	while (ii < Len)
242 	{
243 		const unsigned char c = pData[ii];
244 		if (c == 0x0d)
245 		{
246 			ii++;
247 			continue;
248 		}
249 
250 		if (c == '}' || m_bufferpos == sizeof(m_buffer) - 1)
251 		{
252 			// discard newline, close string, parse line and clear it.
253 			if (m_bufferpos > 0) m_buffer[m_bufferpos] = 0;
254 			ParseLine();
255 			m_bufferpos = 0;
256 		}
257 		else
258 		{
259 			m_buffer[m_bufferpos] = c;
260 			m_bufferpos++;
261 		}
262 		ii++;
263 	}
264 }
265 
SolarMaxGetHexStringValue(const std::string & svalue)266 static unsigned long SolarMaxGetHexStringValue(const std::string &svalue)
267 {
268 	unsigned long ret = -1;
269 	std::stringstream ss;
270 	ss << std::hex << svalue;
271 	ss >> ret;
272 	return ret;
273 }
274 
275 
ParseLine()276 void SolarMaxTCP::ParseLine()
277 {
278 	std::string InputStr = std::string((const char*)&m_buffer);
279 	size_t npos = InputStr.find("|");
280 	if (npos == std::string::npos)
281 	{
282 		_log.Log(LOG_ERROR, "SolarMax: Invalid data received!");
283 		return;
284 	}
285 	InputStr = InputStr.substr(npos + 4);
286 	npos = InputStr.find("|");
287 	if (npos == std::string::npos)
288 	{
289 		_log.Log(LOG_ERROR, "SolarMax: Invalid data received!");
290 		return;
291 	}
292 	InputStr = InputStr.substr(0,npos);
293 
294 	std::vector<std::string> results;
295 	StringSplit(InputStr, ";", results);
296 	if (results.size() < 2)
297 		return; //invalid data
298 
299 	std::vector<std::string>::const_iterator itt;
300 
301 	double kwhCounter = 0;
302 	double ActUsage = 0;
303 
304 	for (itt = results.begin(); itt != results.end(); ++itt)
305 	{
306 		std::vector<std::string> varresults;
307 		StringSplit(*itt, "=", varresults);
308 		if (varresults.size() !=2)
309 			continue;
310 
311 		std::string sLabel = varresults[0];
312 		std::string sVal = varresults[1];
313 
314 		if (sLabel == "KT0")
315 		{
316 			//Energy total
317 			kwhCounter = SolarMaxGetHexStringValue(sVal);// / 10.0f;
318 		}
319 		else if (sLabel == "KDY")
320 		{
321 			//Energy Today
322 		}
323 		else if (sLabel == "PAC")
324 		{
325 			//AC power
326 			ActUsage = SolarMaxGetHexStringValue(sVal)/2.0f;
327 		}
328 		else if (sLabel == "UDC")
329 		{
330 			//DC voltage [mV]
331 			float voltage = float(SolarMaxGetHexStringValue(sVal)) / 10.0f;
332 			SendVoltageSensor(1, 2, 255, voltage, "DC voltage");
333 		}
334 		else if (sLabel == "UL1")
335 		{
336 			//AC voltage [mV]
337 			float voltage = float(SolarMaxGetHexStringValue(sVal)) / 10.0f;
338 			SendVoltageSensor(1, 3, 255, voltage, "AC voltage");
339 		}
340 		else if (sLabel == "IDC")
341 		{
342 			//DC current [mA]
343 			float amps = float(SolarMaxGetHexStringValue(sVal)) / 100.0f;
344 			SendCurrentSensor(4, 255, amps, 0, 0, "DC current");
345 		}
346 		else if (sLabel == "IL1")
347 		{
348 			//AC current [mA]
349 			float amps = float(SolarMaxGetHexStringValue(sVal)) / 100.0f;
350 			SendCurrentSensor(5, 255, amps, 0, 0, "AC current");
351 		}
352 		else if (sLabel == "PIN")
353 		{
354 			//Power installed [mW] (PIN)
355 			//float power_installed = (float)SolarMaxGetHexStringValue(sVal);
356 		}
357 		else if (sLabel == "PRL")
358 		{
359 			//AC power [%]
360 			float percentage = (float)SolarMaxGetHexStringValue(sVal);
361 			SendPercentageSensor(6, 6, 255, percentage, "AC power Percentage");
362 		}
363 		else if (sLabel == "TNF")
364 		{
365 			//AC Frequency (Hz)
366 			float freq = (float)SolarMaxGetHexStringValue(sVal)/100;
367 			SendPercentageSensor(7, 7, 255, freq, "Hz");
368 		}
369 		else if (sLabel == "TKK")
370 		{
371 			//Temperature Heat Sink
372 			float temp = (float)SolarMaxGetHexStringValue(sVal);// / 10.0f;
373 			SendTempSensor(8, 255, temp,"Temperature Heat Sink");
374 		}
375 	}
376 	if (kwhCounter != 0)
377 	{
378 		SendKwhMeterOldWay(1, 1, 255, ActUsage/1000.0f, kwhCounter, "kWh Meter");
379 	}
380 
381 }
382