1 #include "stdafx.h"
2 #include "Thermosmart.h"
3 #include "../main/Helper.h"
4 #include "../main/Logger.h"
5 #include "hardwaretypes.h"
6 #include "../main/localtime_r.h"
7 #include "../main/WebServerHelper.h"
8 #include "../main/RFXtrx.h"
9 #include "../main/SQLHelper.h"
10 #include "../httpclient/HTTPClient.h"
11 #include "../main/mainworker.h"
12 #include "../main/json_helper.h"
13 
14 #define round(a) ( int ) ( a + .5 )
15 
16 const std::string THERMOSMART_LOGIN_PATH = "https://api.thermosmart.com/login";
17 const std::string THERMOSMART_AUTHORISE_PATH = "https://api.thermosmart.com/oauth2/authorize?response_type=code&client_id=client123&redirect_uri=http://clientapp.com/done";
18 const std::string THERMOSMART_DECISION_PATH = "https://api.thermosmart.com/oauth2/authorize/decision";
19 const std::string THERMOSMART_TOKEN_PATH = "https://username:password@api.thermosmart.com/oauth2/token";
20 const std::string THERMOSMART_ACCESS_PATH = "https://api.thermosmart.com/thermostat/[TID]?access_token=[access_token]";
21 const std::string THERMOSMART_SETPOINT_PATH = "https://api.thermosmart.com/thermostat/[TID]?access_token=[access_token]";
22 const std::string THERMOSMART_SET_PAUZE = "https://api.thermosmart.com/thermostat/[TID]/pause?access_token=[access_token]";
23 
24 extern http::server::CWebServerHelper m_webservers;
25 
26 #ifdef _DEBUG
27 	//#define DEBUG_ThermosmartThermostat_read
28 #endif
29 
30 #ifdef DEBUG_ThermosmartThermostat
SaveString2Disk(std::string str,std::string filename)31 void SaveString2Disk(std::string str, std::string filename)
32 {
33 	FILE *fOut = fopen(filename.c_str(), "wb+");
34 	if (fOut)
35 	{
36 		fwrite(str.c_str(), 1, str.size(), fOut);
37 		fclose(fOut);
38 	}
39 }
40 #endif
41 #ifdef DEBUG_ThermosmartThermostat_read
ReadFile(std::string filename)42 std::string ReadFile(std::string filename)
43 {
44 	std::ifstream file;
45 	std::string sResult = "";
46 	file.open(filename.c_str());
47 	if (!file.is_open())
48 		return "";
49 	std::string sLine;
50 	while (!file.eof())
51 	{
52 		getline(file, sLine);
53 		sResult += sLine;
54 	}
55 	file.close();
56 	return sResult;
57 }
58 #endif
59 
CThermosmart(const int ID,const std::string & Username,const std::string & Password,const int Mode1,const int Mode2,const int Mode3,const int Mode4,const int Mode5,const int Mode6)60 CThermosmart::CThermosmart(const int ID, const std::string &Username, const std::string &Password, const int Mode1, const int Mode2, const int Mode3, const int Mode4, const int Mode5, const int Mode6)
61 {
62 	if ((Password == "secret")|| (Password.empty()))
63 	{
64 		_log.Log(LOG_ERROR, "Thermosmart: Please update your username/password!...");
65 	}
66 	else
67 	{
68 		m_UserName = Username;
69 		m_Password = Password;
70 		stdstring_trim(m_UserName);
71 		stdstring_trim(m_Password);
72 	}
73 	m_HwdID=ID;
74 	m_OutsideTemperatureIdx = 0; //use build in
75 	m_LastMinute = -1;
76 	SetModes(Mode1, Mode2, Mode3, Mode4, Mode5, Mode6);
77 	Init();
78 }
79 
~CThermosmart(void)80 CThermosmart::~CThermosmart(void)
81 {
82 }
83 
SetModes(const int Mode1,const int Mode2,const int Mode3,const int Mode4,const int Mode5,const int Mode6)84 void CThermosmart::SetModes(const int Mode1, const int Mode2, const int Mode3, const int Mode4, const int Mode5, const int Mode6)
85 {
86 	m_OutsideTemperatureIdx = Mode1;
87 }
88 
Init()89 void CThermosmart::Init()
90 {
91 	m_AccessToken = "";
92 	m_ThermostatID = "";
93 	m_bDoLogin = true;
94 }
95 
StartHardware()96 bool CThermosmart::StartHardware()
97 {
98 	RequestStart();
99 
100 	Init();
101 	m_LastMinute = -1;
102 	//Start worker thread
103 	m_thread = std::make_shared<std::thread>(&CThermosmart::Do_Work, this);
104 	SetThreadNameInt(m_thread->native_handle());
105 	m_bIsStarted=true;
106 	sOnConnected(this);
107 	return (m_thread != nullptr);
108 }
109 
StopHardware()110 bool CThermosmart::StopHardware()
111 {
112 	if (m_thread)
113 	{
114 		RequestStop();
115 		m_thread->join();
116 		m_thread.reset();
117 	}
118     m_bIsStarted=false;
119     return true;
120 }
121 
122 #define THERMOSMART_POLL_INTERVAL 30
123 
Do_Work()124 void CThermosmart::Do_Work()
125 {
126 	_log.Log(LOG_STATUS,"Thermosmart: Worker started...");
127 	int sec_counter = THERMOSMART_POLL_INTERVAL-5;
128 	while (!IsStopRequested(1000))
129 	{
130 		sec_counter++;
131 		if (sec_counter % 12 == 0) {
132 			m_LastHeartbeat=mytime(NULL);
133 		}
134 		if (sec_counter % THERMOSMART_POLL_INTERVAL == 0)
135 		{
136 			SendOutsideTemperature();
137 			GetMeterDetails();
138 		}
139 	}
140 	Logout();
141 
142 	_log.Log(LOG_STATUS,"Thermosmart: Worker stopped...");
143 }
144 
GetOutsideTemperatureFromDomoticz(float & tvalue)145 bool CThermosmart::GetOutsideTemperatureFromDomoticz(float &tvalue)
146 {
147 	if (m_OutsideTemperatureIdx == 0)
148 		return false;
149 	Json::Value tempjson;
150 	m_webservers.GetJSonDevices(tempjson, "", "temp", "ID", std::to_string(m_OutsideTemperatureIdx), "", "", true, false, false, 0, "");
151 
152 	size_t tsize = tempjson.size();
153 	if (tsize < 1)
154 		return false;
155 
156 	Json::Value::const_iterator itt;
157 	Json::ArrayIndex rsize = tempjson["result"].size();
158 	if (rsize < 1)
159 		return false;
160 
161 	bool bHaveTimeout = tempjson["result"][0]["HaveTimeout"].asBool();
162 	if (bHaveTimeout)
163 		return false;
164 	tvalue = tempjson["result"][0]["Temp"].asFloat();
165 	return true;
166 }
167 
SendSetPointSensor(const unsigned char Idx,const float Temp,const std::string & defaultname)168 void CThermosmart::SendSetPointSensor(const unsigned char Idx, const float Temp, const std::string &defaultname)
169 {
170 	_tThermostat thermos;
171 	thermos.subtype=sTypeThermSetpoint;
172 	thermos.id1=0;
173 	thermos.id2=0;
174 	thermos.id3=0;
175 	thermos.id4=Idx;
176 	thermos.dunit=0;
177 	thermos.temp=Temp;
178 	sDecodeRXMessage(this, (const unsigned char *)&thermos, "Setpoint", 255);
179 }
180 
Login()181 bool CThermosmart::Login()
182 {
183 	if (!m_AccessToken.empty())
184 	{
185 		Logout();
186 	}
187 	if (m_UserName.empty())
188 		return false;
189 	m_AccessToken = "";
190 	m_ThermostatID = "";
191 
192 	std::string sURL;
193 	std::stringstream sstr;
194 	sstr << "username=" << m_UserName << "&password=" << m_Password;
195 	std::string szPostdata=sstr.str();
196 	std::vector<std::string> ExtraHeaders;
197 	std::string sResult;
198 
199 	//# 1. Login
200 
201 	sURL = THERMOSMART_LOGIN_PATH;
202 	if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult))
203 		{
204 			_log.Log(LOG_ERROR,"Thermosmart: Error login!");
205 			return false;
206 		}
207 
208 #ifdef DEBUG_ThermosmartThermostat
209 	SaveString2Disk(sResult, "E:\\thermosmart1.txt");
210 #endif
211 
212 	//# 2. Get Authorize Dialog
213 	sURL = THERMOSMART_AUTHORISE_PATH;
214 	stdreplace(sURL, "client123", "api-rob-b130d8f5123bf24b");
215 	ExtraHeaders.clear();
216 	if (!HTTPClient::GET(sURL, sResult))
217 	{
218 		_log.Log(LOG_ERROR, "Thermosmart: Error login!");
219 		return false;
220 	}
221 
222 #ifdef DEBUG_ThermosmartThermostat
223 	SaveString2Disk(sResult, "E:\\thermosmart2.txt");
224 #endif
225 
226 	size_t tpos = sResult.find("value=");
227 	if (tpos == std::string::npos)
228 	{
229 		_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
230 		return false;
231 	}
232 	sResult = sResult.substr(tpos + 7);
233 	tpos = sResult.find("\">");
234 	if (tpos == std::string::npos)
235 	{
236 		_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
237 		return false;
238 	}
239 	std::string TID = sResult.substr(0, tpos);
240 
241 	//# 3. Authorize  (read out transaction_id from the HTML form received in the previous step). transaction_id prevents from XSRF attacks.
242 	szPostdata = "transaction_id=" + TID;
243 	ExtraHeaders.clear();
244 	sURL = THERMOSMART_DECISION_PATH;
245 	if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult, false))
246 	{
247 		_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
248 		return false;
249 	}
250 
251 #ifdef DEBUG_ThermosmartThermostat
252 	SaveString2Disk(sResult, "E:\\thermosmart3.txt");
253 #endif
254 
255 	tpos = sResult.find("code=");
256 	if (tpos == std::string::npos)
257 	{
258 		_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
259 		return false;
260 	}
261 	std::string CODE = sResult.substr(tpos + 5);
262 
263 	//# 4. Exchange authorization code for Access token (read out the code from the previous response)
264 	szPostdata = "grant_type=authorization_code&code=" + CODE + "&redirect_uri=http://clientapp.com/done";
265 	sURL = THERMOSMART_TOKEN_PATH;
266 
267 	stdreplace(sURL, "username", "api-rob-b130d8f5123bf24b");
268 	stdreplace(sURL, "password", "c1d91661eef0bc4fa2ac67fd");
269 
270 	if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult, false))
271 	{
272 		_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
273 		return false;
274 	}
275 
276 #ifdef DEBUG_ThermosmartThermostat
277 	SaveString2Disk(sResult, "E:\\thermosmart4.txt");
278 #endif
279 
280 	Json::Value root;
281 	bool ret = ParseJSon(sResult, root);
282 	if ((!ret) || (!root.isObject()))
283 	{
284 		_log.Log(LOG_ERROR, "Thermosmart: Invalid/no data received...");
285 		return false;
286 	}
287 
288 	if (root["access_token"].empty()||root["thermostat"].empty())
289 	{
290 		_log.Log(LOG_ERROR, "Thermosmart: No access granted, check username/password...");
291 		return false;
292 	}
293 
294 	m_AccessToken = root["access_token"].asString();
295 	m_ThermostatID = root["thermostat"].asString();
296 
297 	_log.Log(LOG_STATUS, "Thermosmart: Login successfull!...");
298 
299 	m_bDoLogin = false;
300 	return true;
301 }
302 
Logout()303 void CThermosmart::Logout()
304 {
305 	if (m_bDoLogin)
306 		return; //we are not logged in
307 	m_AccessToken = "";
308 	m_ThermostatID = "";
309 	m_bDoLogin = true;
310 }
311 
312 
WriteToHardware(const char * pdata,const unsigned char length)313 bool CThermosmart::WriteToHardware(const char *pdata, const unsigned char length)
314 {
315 	const tRBUF *pCmd = reinterpret_cast<const tRBUF *>(pdata);
316 	if (pCmd->LIGHTING2.packettype == pTypeLighting2)
317 	{
318 		//Light command
319 
320 		int node_id = pCmd->LIGHTING2.id4;
321 		bool bIsOn = (pCmd->LIGHTING2.cmnd == light2_sOn);
322 		if (node_id == 1)
323 		{
324 			//Pause Switch
325 			SetPauseStatus(bIsOn);
326 			return true;
327 		}
328 	}
329 	return false;
330 }
331 
GetMeterDetails()332 void CThermosmart::GetMeterDetails()
333 {
334 	if (m_UserName.empty() || m_Password.empty() )
335 		return;
336 	std::string sResult;
337 #ifdef DEBUG_ThermosmartThermostat_read
338 	sResult = ReadFile("E:\\thermosmart_getdata.txt");
339 #else
340 	if (m_bDoLogin)
341 	{
342 		if (!Login())
343 			return;
344 	}
345 	std::string sURL = THERMOSMART_ACCESS_PATH;
346 	stdreplace(sURL, "[TID]", m_ThermostatID);
347 	stdreplace(sURL, "[access_token]", m_AccessToken);
348 	if (!HTTPClient::GET(sURL, sResult))
349 	{
350 		_log.Log(LOG_ERROR, "Thermosmart: Error getting thermostat data!");
351 		m_bDoLogin = true;
352 		return;
353 	}
354 
355 #ifdef DEBUG_ThermosmartThermostat
356 	SaveString2Disk(sResult, "E:\\thermosmart_getdata.txt");
357 #endif
358 #endif
359 	Json::Value root;
360 	bool ret = ParseJSon(sResult, root);
361 	if ((!ret) || (!root.isObject()))
362 	{
363 		_log.Log(LOG_ERROR, "Thermosmart: Invalid/no data received...");
364 		m_bDoLogin = true;
365 		return;
366 	}
367 
368 	if (root["target_temperature"].empty() || root["room_temperature"].empty())
369 	{
370 		_log.Log(LOG_ERROR, "Thermosmart: Invalid/no data received...");
371 		m_bDoLogin = true;
372 		return;
373 	}
374 
375 	float temperature;
376 	temperature = (float)root["target_temperature"].asFloat();
377 	SendSetPointSensor(1, temperature, "target temperature");
378 
379 	temperature = (float)root["room_temperature"].asFloat();
380 	SendTempSensor(2, 255, temperature, "room temperature");
381 
382 	if (!root["outside_temperature"].empty())
383 	{
384 		temperature = (float)root["outside_temperature"].asFloat();
385 		SendTempSensor(3, 255, temperature, "outside temperature");
386 	}
387 	if (!root["source"].empty())
388 	{
389 		std::string actSource = root["source"].asString();
390 		bool bPauzeOn = (actSource == "pause");
391 		SendSwitch(1, 1, 255, bPauzeOn, 0, "Thermostat Pause");
392 	}
393 }
394 
SetSetpoint(const int idx,const float temp)395 void CThermosmart::SetSetpoint(const int idx, const float temp)
396 {
397 	if (m_bDoLogin)
398 	{
399 		if (!Login())
400 			return;
401 	}
402 
403 	char szTemp[20];
404 	sprintf(szTemp, "%.1f", temp);
405 	std::string sTemp = szTemp;
406 
407 	std::string szPostdata = "target_temperature=" + sTemp;
408 	std::vector<std::string> ExtraHeaders;
409 	std::string sResult;
410 
411 	std::string sURL = THERMOSMART_SETPOINT_PATH;
412 	stdreplace(sURL, "[TID]", m_ThermostatID);
413 	stdreplace(sURL, "[access_token]", m_AccessToken);
414 	if (!HTTPClient::PUT(sURL, szPostdata, ExtraHeaders, sResult))
415 	{
416 		_log.Log(LOG_ERROR, "Thermosmart: Error setting thermostat data!");
417 		m_bDoLogin = true;
418 		return;
419 	}
420 	SendSetPointSensor(1, temp, "target temperature");
421 }
422 
SetPauseStatus(const bool bIsPause)423 void CThermosmart::SetPauseStatus(const bool bIsPause)
424 {
425 	if (m_bDoLogin)
426 	{
427 		if (!Login())
428 			return;
429 	}
430 
431 	std::string szPostdata = "{\"pause\":";
432 	szPostdata += (bIsPause) ? "true" : "false";
433 	szPostdata += "}";
434 
435 	std::vector<std::string> ExtraHeaders;
436 	ExtraHeaders.push_back("Content-Type: application/json");
437 	std::string sResult;
438 
439 	std::string sURL = THERMOSMART_SET_PAUZE;
440 	stdreplace(sURL, "[TID]", m_ThermostatID);
441 	stdreplace(sURL, "[access_token]", m_AccessToken);
442 
443 	if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult))
444 	{
445 		_log.Log(LOG_ERROR, "Thermosmart: Error setting Pause status!");
446 		m_bDoLogin = true;
447 		return;
448 	}
449 }
450 
SendOutsideTemperature()451 void CThermosmart::SendOutsideTemperature()
452 {
453 	float temp;
454 	if (!GetOutsideTemperatureFromDomoticz(temp))
455 		return;
456 	SetOutsideTemp(temp);
457 }
458 
SetOutsideTemp(const float temp)459 void CThermosmart::SetOutsideTemp(const float temp)
460 {
461 	if (m_bDoLogin)
462 	{
463 		if (!Login())
464 			return;
465 	}
466 
467 	char szTemp[20];
468 	sprintf(szTemp, "%.1f", temp);
469 	std::string sTemp = szTemp;
470 
471 	std::string szPostdata = "outside_temperature=" + sTemp;
472 	std::vector<std::string> ExtraHeaders;
473 	std::string sResult;
474 
475 	std::string sURL = THERMOSMART_SETPOINT_PATH;
476 	stdreplace(sURL, "[TID]", m_ThermostatID);
477 	stdreplace(sURL, "[access_token]", m_AccessToken);
478 	if (!HTTPClient::PUT(sURL, szPostdata, ExtraHeaders, sResult))
479 	{
480 		_log.Log(LOG_ERROR, "Thermosmart: Error setting thermostat data!");
481 		m_bDoLogin = true;
482 		return;
483 	}
484 }
485 
486