1 #include "stdafx.h"
2 #include "Nest.h"
3 #include "../main/Helper.h"
4 #include "../main/Logger.h"
5 #include "hardwaretypes.h"
6 #include "../main/localtime_r.h"
7 #include "../main/RFXtrx.h"
8 #include "../main/SQLHelper.h"
9 #include "../httpclient/HTTPClient.h"
10 #include "../main/mainworker.h"
11 #include "../main/json_helper.h"
12 
13 #define round(a) ( int ) ( a + .5 )
14 
15 const std::string NEST_LOGIN_PATH = "https://home.nest.com/user/login";
16 const std::string NEST_GET_STATUS = "/v2/mobile/user.";
17 const std::string NEST_SET_SHARED = "/v2/put/shared.";
18 const std::string NEST_SET_STRUCTURE = "/v2/put/structure.";
19 const std::string NEST_SET_DEVICE = "/v2/put/device.";
20 
21 #define NEST_USER_AGENT_STRING "User-Agent: Nest/3.0.1.15"
22 
23 #ifdef _DEBUG
24 	//#define DEBUG_NextThermostatR
25 	//#define DEBUG_NextThermostatW
26 #endif
27 
28 #ifdef DEBUG_NextThermostatW
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 #ifdef DEBUG_NextThermostatR
ReadFile(std::string filename)40 std::string ReadFile(std::string filename)
41 {
42 	std::ifstream file;
43 	std::string sResult = "";
44 	file.open(filename.c_str());
45 	if (!file.is_open())
46 		return "";
47 	std::string sLine;
48 	while (!file.eof())
49 	{
50 		getline(file, sLine);
51 		sResult += sLine;
52 	}
53 	file.close();
54 	return sResult;
55 }
56 #endif
57 
CNest(const int ID,const std::string & Username,const std::string & Password)58 CNest::CNest(const int ID, const std::string &Username, const std::string &Password) :
59 m_UserName(CURLEncode::URLEncode(Username)),
60 m_Password(CURLEncode::URLEncode(Password))
61 {
62 	m_HwdID=ID;
63 	Init();
64 }
65 
~CNest(void)66 CNest::~CNest(void)
67 {
68 }
69 
Init()70 void CNest::Init()
71 {
72 	m_AccessToken = "";
73 	m_UserID = "";
74 	m_bDoLogin = true;
75 }
76 
StartHardware()77 bool CNest::StartHardware()
78 {
79 	RequestStart();
80 
81 	Init();
82 	//Start worker thread
83 	m_thread = std::make_shared<std::thread>(&CNest::Do_Work, this);
84 	SetThreadNameInt(m_thread->native_handle());
85 	m_bIsStarted=true;
86 	sOnConnected(this);
87 	return (m_thread != nullptr);
88 }
89 
StopHardware()90 bool CNest::StopHardware()
91 {
92 	if (m_thread)
93 	{
94 		RequestStop();
95 		m_thread->join();
96 		m_thread.reset();
97 	}
98     m_bIsStarted=false;
99     return true;
100 }
101 
102 #define NEST_POLL_INTERVAL 60
103 
Do_Work()104 void CNest::Do_Work()
105 {
106 	_log.Log(LOG_STATUS,"Nest: Worker started...");
107 	int sec_counter = NEST_POLL_INTERVAL-5;
108 	while (!IsStopRequested(1000))
109 	{
110 		sec_counter++;
111 		if (sec_counter % 12 == 0)
112 		{
113 			m_LastHeartbeat = mytime(NULL);
114 		}
115 
116 		if (sec_counter % NEST_POLL_INTERVAL == 0)
117 		{
118 			GetMeterDetails();
119 		}
120 
121 	}
122 	Logout();
123 	_log.Log(LOG_STATUS,"Nest: Worker stopped...");
124 }
125 
SendSetPointSensor(const unsigned char Idx,const float Temp,const std::string & defaultname)126 void CNest::SendSetPointSensor(const unsigned char Idx, const float Temp, const std::string &defaultname)
127 {
128 	_tThermostat thermos;
129 	thermos.subtype=sTypeThermSetpoint;
130 	thermos.id1=0;
131 	thermos.id2=0;
132 	thermos.id3=0;
133 	thermos.id4=Idx;
134 	thermos.dunit=0;
135 
136 	thermos.temp=Temp;
137 
138 	sDecodeRXMessage(this, (const unsigned char *)&thermos, defaultname.c_str(), 255);
139 }
140 
141 
142 // Creates and updates switch used to log Heating and/or Colling.
UpdateSwitch(const unsigned char Idx,const bool bOn,const std::string & defaultname)143 void CNest::UpdateSwitch(const unsigned char Idx, const bool bOn, const std::string &defaultname)
144 {
145 	char szIdx[10];
146 	sprintf(szIdx, "%X%02X%02X%02X", 0, 0, 0, Idx);
147 	std::vector<std::vector<std::string> > result;
148 	result = m_sql.safe_query("SELECT Name,nValue,sValue FROM DeviceStatus WHERE (HardwareID==%d) AND (Type==%d) AND (SubType==%d) AND (DeviceID=='%q')",
149 		m_HwdID, pTypeLighting2, sTypeAC, szIdx);
150 	if (!result.empty())
151 	{
152 		//check if we have a change, if not do not update it
153 		int nvalue = atoi(result[0][1].c_str());
154 		if ((!bOn) && (nvalue == 0))
155 			return;
156 		if ((bOn && (nvalue != 0)))
157 			return;
158 	}
159 
160 	//Send as Lighting 2
161 	tRBUF lcmd;
162 	memset(&lcmd, 0, sizeof(RBUF));
163 	lcmd.LIGHTING2.packetlength = sizeof(lcmd.LIGHTING2) - 1;
164 	lcmd.LIGHTING2.packettype = pTypeLighting2;
165 	lcmd.LIGHTING2.subtype = sTypeAC;
166 	lcmd.LIGHTING2.id1 = 0;
167 	lcmd.LIGHTING2.id2 = 0;
168 	lcmd.LIGHTING2.id3 = 0;
169 	lcmd.LIGHTING2.id4 = Idx;
170 	lcmd.LIGHTING2.unitcode = 1;
171 	int level = 15;
172 	if (!bOn)
173 	{
174 		level = 0;
175 		lcmd.LIGHTING2.cmnd = light2_sOff;
176 	}
177 	else
178 	{
179 		level = 15;
180 		lcmd.LIGHTING2.cmnd = light2_sOn;
181 	}
182 	lcmd.LIGHTING2.level = (BYTE)level;
183 	lcmd.LIGHTING2.filler = 0;
184 	lcmd.LIGHTING2.rssi = 12;
185 	sDecodeRXMessage(this, (const unsigned char *)&lcmd.LIGHTING2, defaultname.c_str(), 255);
186 }
187 
Login()188 bool CNest::Login()
189 {
190 	if (!m_AccessToken.empty())
191 	{
192 		Logout();
193 	}
194 	m_AccessToken = "";
195 	m_UserID = "";
196 
197 	std::stringstream sstr;
198 	sstr << "username=" << m_UserName << "&password=" << m_Password;
199 	std::string szPostdata=sstr.str();
200 	std::vector<std::string> ExtraHeaders;
201 	ExtraHeaders.push_back(NEST_USER_AGENT_STRING);
202 	std::string sResult;
203 
204 	std::string sURL = NEST_LOGIN_PATH;
205 	if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult))
206 	{
207 		_log.Log(LOG_ERROR,"Nest: Error login!");
208 		return false;
209 	}
210 
211 	Json::Value root;
212 	bool bRet = ParseJSon(sResult, root);
213 	if ((!bRet) || (!root.isObject()))
214 	{
215 		_log.Log(LOG_ERROR, "Nest: Invalid data received, or invalid username/password!");
216 		return false;
217 	}
218 	if (root["urls"].empty())
219 	{
220 		_log.Log(LOG_ERROR, "Nest: Invalid data received, or invalid username/password!");
221 		return false;
222 	}
223 	if (root["urls"]["transport_url"].empty())
224 	{
225 		_log.Log(LOG_ERROR, "Nest: Invalid data received, or invalid username/password!");
226 		return false;
227 	}
228 	m_TransportURL = root["urls"]["transport_url"].asString();
229 
230 	if (root["access_token"].empty())
231 	{
232 		_log.Log(LOG_ERROR, "Nest: Invalid data received, or invalid username/password!");
233 		return false;
234 	}
235 	m_AccessToken = root["access_token"].asString();
236 
237 	if (root["userid"].empty())
238 	{
239 		_log.Log(LOG_ERROR, "Nest: Invalid data received, or invalid username/password!");
240 		return false;
241 	}
242 	m_UserID = root["userid"].asString();
243 
244 	m_bDoLogin = false;
245 	return true;
246 }
247 
Logout()248 void CNest::Logout()
249 {
250 	if (m_bDoLogin)
251 		return; //we are not logged in
252 	m_AccessToken = "";
253 	m_UserID = "";
254 	m_bDoLogin = true;
255 }
256 
257 
WriteToHardware(const char * pdata,const unsigned char)258 bool CNest::WriteToHardware(const char *pdata, const unsigned char /*length*/)
259 {
260 	if (m_UserName.size() == 0)
261 		return false;
262 	if (m_Password.size() == 0)
263 		return false;
264 
265 	const tRBUF *pCmd = reinterpret_cast<const tRBUF *>(pdata);
266 	if (pCmd->LIGHTING2.packettype != pTypeLighting2)
267 		return false; //later add RGB support, if someone can provide access
268 
269 	int node_id = pCmd->LIGHTING2.id4;
270 
271 	bool bIsOn = (pCmd->LIGHTING2.cmnd == light2_sOn);
272 
273 	if (node_id % 3 == 0)
274 	{
275 		//Away
276 		return SetAway((uint8_t)node_id, bIsOn);
277 	}
278 
279 	if (node_id % 4 == 0)
280 	{
281 		//Manual Eco Mode
282 		return SetManualEcoMode((uint8_t)node_id, bIsOn);
283 	}
284 
285 	return false;
286 }
287 
UpdateSmokeSensor(const unsigned char Idx,const bool bOn,const std::string & defaultname)288 void CNest::UpdateSmokeSensor(const unsigned char Idx, const bool bOn, const std::string &defaultname)
289 {
290 	bool bDeviceExits = true;
291 	char szIdx[10];
292 	sprintf(szIdx, "%X%02X%02X%02X", 0, 0, Idx, 0);
293 	std::vector<std::vector<std::string> > result;
294 	result = m_sql.safe_query("SELECT Name,nValue,sValue FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%q')", m_HwdID, szIdx);
295 	if (result.empty())
296 	{
297 		bDeviceExits = false;
298 	}
299 	else
300 	{
301 		//check if we have a change, if not only update the LastUpdate field
302 		bool bNoChange = false;
303 		int nvalue = atoi(result[0][1].c_str());
304 		if ((!bOn) && (nvalue == 0))
305 			bNoChange = true;
306 		else if ((bOn && (nvalue != 0)))
307 			bNoChange = true;
308 		if (bNoChange)
309 		{
310 			time_t now = time(0);
311 			struct tm ltime;
312 			localtime_r(&now, &ltime);
313 
314 			char szLastUpdate[40];
315 			sprintf(szLastUpdate, "%04d-%02d-%02d %02d:%02d:%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday, ltime.tm_hour, ltime.tm_min, ltime.tm_sec);
316 
317 			m_sql.safe_query("UPDATE DeviceStatus SET LastUpdate='%q' WHERE(HardwareID == %d) AND (DeviceID == '%q')",
318 				szLastUpdate, m_HwdID, szIdx);
319 			return;
320 		}
321 	}
322 
323 	//Send as Lighting 2
324 	tRBUF lcmd;
325 	memset(&lcmd, 0, sizeof(RBUF));
326 	lcmd.LIGHTING2.packetlength = sizeof(lcmd.LIGHTING2) - 1;
327 	lcmd.LIGHTING2.packettype = pTypeLighting2;
328 	lcmd.LIGHTING2.subtype = sTypeAC;
329 	lcmd.LIGHTING2.id1 = 0;
330 	lcmd.LIGHTING2.id2 = 0;
331 	lcmd.LIGHTING2.id3 = Idx;
332 	lcmd.LIGHTING2.id4 = 0;
333 	lcmd.LIGHTING2.unitcode = 1;
334 	int level = 15;
335 	if (!bOn)
336 	{
337 		level = 0;
338 		lcmd.LIGHTING2.cmnd = light2_sOff;
339 	}
340 	else
341 	{
342 		level = 15;
343 		lcmd.LIGHTING2.cmnd = light2_sOn;
344 	}
345 	lcmd.LIGHTING2.level = (uint8_t)level;
346 	lcmd.LIGHTING2.filler = 0;
347 	lcmd.LIGHTING2.rssi = 12;
348 
349 	if (!bDeviceExits)
350 	{
351 		m_mainworker.PushAndWaitRxMessage(this, (const unsigned char *)&lcmd.LIGHTING2, defaultname.c_str(), 255);
352 		//Assign default name for device
353 		m_sql.safe_query("UPDATE DeviceStatus SET Name='%q' WHERE (HardwareID==%d) AND (DeviceID=='%q')", defaultname.c_str(), m_HwdID, szIdx);
354 		result = m_sql.safe_query("SELECT ID FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%q')", m_HwdID, szIdx);
355 		if (!result.empty())
356 		{
357 			m_sql.safe_query("UPDATE DeviceStatus SET SwitchType=%d WHERE (ID=='%q')", STYPE_SMOKEDETECTOR, result[0][0].c_str());
358 		}
359 	}
360 	else
361 		sDecodeRXMessage(this, (const unsigned char *)&lcmd.LIGHTING2, defaultname.c_str(), 255);
362 }
363 
364 
GetMeterDetails()365 void CNest::GetMeterDetails()
366 {
367 	std::string sResult;
368 #ifdef DEBUG_NextThermostatR
369 	sResult = ReadFile("E:\\nest.json");
370 #else
371 	if (m_UserName.size()==0)
372 		return;
373 	if (m_Password.size()==0)
374 		return;
375 	if (m_bDoLogin)
376 	{
377 		if (!Login())
378 		return;
379 	}
380 	std::vector<std::string> ExtraHeaders;
381 
382 	ExtraHeaders.push_back(NEST_USER_AGENT_STRING);
383 	ExtraHeaders.push_back("Authorization:Basic " + m_AccessToken);
384 	ExtraHeaders.push_back("X-nl-user-id:" + m_UserID);
385 	ExtraHeaders.push_back("X-nl-protocol-version:1");
386 
387 	//Get Data
388 	std::string sURL = m_TransportURL + NEST_GET_STATUS + m_UserID;
389 	if (!HTTPClient::GET(sURL, ExtraHeaders, sResult))
390 	{
391 		_log.Log(LOG_ERROR, "Nest: Error getting current state!");
392 		m_bDoLogin = true;
393 		return;
394 	}
395 #endif
396 
397 #ifdef DEBUG_NextThermostatW
398 	SaveString2Disk(sResult, "E:\\nest.json");
399 #endif
400 
401 	Json::Value root;
402 	bool bRet = ParseJSon(sResult, root);
403 	if ((!bRet) || (!root.isObject()))
404 	{
405 		_log.Log(LOG_ERROR, "Nest: Invalid data received!");
406 		m_bDoLogin = true;
407 		return;
408 	}
409 	bool bHaveShared = !root["shared"].empty();
410 	bool bHaveTopaz = !root["topaz"].empty();
411 
412 	if ((!bHaveShared) && (!bHaveTopaz))
413 	{
414 		_log.Log(LOG_ERROR, "Nest: request not successful, restarting..!");
415 		m_bDoLogin = true;
416 		return;
417 	}
418 
419 	//Protect
420 	if (bHaveTopaz)
421 	{
422 		if (root["topaz"].size() < 1)
423 		{
424 			_log.Log(LOG_ERROR, "Nest: request not successful, restarting..!");
425 			m_bDoLogin = true;
426 			return;
427 		}
428 		Json::Value::Members members = root["topaz"].getMemberNames();
429 		if (members.size() < 1)
430 		{
431 			_log.Log(LOG_ERROR, "Nest: request not successful, restarting..!");
432 			m_bDoLogin = true;
433 			return;
434 		}
435 		int SwitchIndex = 1;
436 		for (Json::Value::iterator itDevice = root["topaz"].begin(); itDevice != root["topaz"].end(); ++itDevice)
437 		{
438 			Json::Value device = *itDevice;
439 			std::string devstring = itDevice.key().asString();
440 			if (device["where_id"].empty())
441 				continue;
442 			std::string whereid = device["where_id"].asString();
443 			//lookup name
444 			std::string devName = devstring;
445 			if (!root["where"].empty())
446 			{
447 				for (Json::Value::iterator itWhere = root["where"].begin(); itWhere != root["where"].end(); ++itWhere)
448 				{
449 					Json::Value iwhere = *itWhere;
450 					if (!iwhere["wheres"].empty())
451 					{
452 						for (Json::Value::iterator itWhereNest = iwhere["wheres"].begin(); itWhereNest != iwhere["wheres"].end(); ++itWhereNest)
453 						{
454 							Json::Value iwhereItt = *itWhereNest;
455 							if (!iwhereItt["where_id"].empty())
456 							{
457 								std::string tmpWhereid = iwhereItt["where_id"].asString();
458 								if (tmpWhereid == whereid)
459 								{
460 									devName = iwhereItt["name"].asString();
461 									break;
462 								}
463 							}
464 						}
465 					}
466 
467 				}
468 			}
469 			bool bIAlarm = false;
470 			bool bBool;
471 			if (!device["component_speaker_test_passed"].empty())
472 			{
473 				bBool = device["component_speaker_test_passed"].asBool();
474 				if (!bBool)
475 					bIAlarm = true;
476 			}
477 			if (!device["component_smoke_test_passed"].empty())
478 			{
479 				bBool = device["component_smoke_test_passed"].asBool();
480 				if (!bBool)
481 					bIAlarm = true;
482 			}
483 /*
484 			if (!device["component_heat_test_passed"].empty())
485 			{
486 				bBool = device["component_heat_test_passed"].asBool();
487 				if (!bBool)
488 					bIAlarm = true;
489 			}
490 */
491 			if (!device["component_buzzer_test_passed"].empty())
492 			{
493 				bBool = device["component_buzzer_test_passed"].asBool();
494 				if (!bBool)
495 					bIAlarm = true;
496 			}
497 /*
498 			if (!device["component_us_test_passed"].empty())
499 			{
500 				bBool = device["component_us_test_passed"].asBool();
501 				if (!bBool)
502 					bIAlarm = true;
503 			}
504 */
505 			if (!device["component_temp_test_passed"].empty())
506 			{
507 				bBool = device["component_temp_test_passed"].asBool();
508 				if (!bBool)
509 					bIAlarm = true;
510 			}
511 			if (!device["component_wifi_test_passed"].empty())
512 			{
513 				bBool = device["component_wifi_test_passed"].asBool();
514 				if (!bBool)
515 					bIAlarm = true;
516 			}
517 			if (!device["component_als_test_passed"].empty())
518 			{
519 				bBool = device["component_als_test_passed"].asBool();
520 				if (!bBool)
521 					bIAlarm = true;
522 			}
523 			if (!device["component_co_test_passed"].empty())
524 			{
525 				bBool = device["component_co_test_passed"].asBool();
526 				if (!bBool)
527 					bIAlarm = true;
528 			}
529 			if (!device["component_hum_test_passed"].empty())
530 			{
531 				bBool = device["component_hum_test_passed"].asBool();
532 				if (!bBool)
533 					bIAlarm = true;
534 			}
535 			UpdateSmokeSensor((uint8_t)SwitchIndex, bIAlarm, devName);
536 			SwitchIndex++;
537 		}
538 	}
539 
540 	//Thermostat
541 	if (!bHaveShared)
542 		return;
543 	if (root["shared"].size()<1)
544 	{
545 		if (bHaveTopaz)
546 			return;
547 		_log.Log(LOG_ERROR, "Nest: request not successful, restarting..!");
548 		m_bDoLogin = true;
549 		return;
550 	}
551 
552 	size_t iThermostat = 0;
553 	for (Json::Value::iterator ittStructure = root["structure"].begin(); ittStructure != root["structure"].end(); ++ittStructure)
554 	{
555 		Json::Value nstructure = *ittStructure;
556 		if (!nstructure.isObject())
557 			continue;
558 		std::string StructureID = ittStructure.key().asString();
559 		std::string StructureName = nstructure["name"].asString();
560 
561 		for (Json::Value::iterator ittDevice = nstructure["devices"].begin(); ittDevice != nstructure["devices"].end(); ++ittDevice)
562 		{
563 			std::string devID = (*ittDevice).asString();
564 			if (devID.find("device.")==std::string::npos)
565 				continue;
566 			std::string Serial = devID.substr(7);
567 			if (root["device"].empty())
568 				continue;
569 			if (root["device"][Serial].empty())
570 				continue; //not found !?
571 			if (root["shared"][Serial].empty())
572 				continue; //Nothing shared?
573 
574 
575 			Json::Value ndevice = root["device"][Serial];
576 			if (!ndevice.isObject())
577 				continue;
578 
579 			std::string Name = "Thermostat";
580 			if (!ndevice["where_id"].empty())
581 			{
582 				//Lookup our 'where' (for the Name of the thermostat)
583 				std::string where_id = ndevice["where_id"].asString();
584 
585 				if (!root["where"].empty())
586 				{
587 					if (!root["where"][StructureID].empty())
588 					{
589 						for (Json::Value::iterator ittWheres = root["where"][StructureID]["wheres"].begin(); ittWheres != root["where"][StructureID]["wheres"].end(); ++ittWheres)
590 						{
591 							Json::Value nwheres = *ittWheres;
592 							if (nwheres["where_id"] == where_id)
593 							{
594 								Name = StructureName + " " + nwheres["name"].asString();
595 								break;
596 							}
597 						}
598 					}
599 				}
600 			}
601 
602 			_tNestThemostat ntherm;
603 			ntherm.Serial = Serial;
604 			ntherm.StructureID = StructureID;
605 			ntherm.Name = Name;
606 			m_thermostats[iThermostat] = ntherm;
607 
608 			Json::Value nshared = root["shared"][Serial];
609 
610 			//Setpoint
611 			if (!nshared["target_temperature"].empty())
612 			{
613 				float currentSetpoint = nshared["target_temperature"].asFloat();
614 				SendSetPointSensor((const unsigned char)(iThermostat * 3) + 1, currentSetpoint, Name + " Setpoint");
615 			}
616 			//Room Temperature/Humidity
617 			if (!nshared["current_temperature"].empty())
618 			{
619 				float currentTemp = nshared["current_temperature"].asFloat();
620 				int Humidity = root["device"][Serial]["current_humidity"].asInt();
621 				SendTempHumSensor((iThermostat * 3) + 2, 255, currentTemp, Humidity, Name + " TempHum");
622 			}
623 
624 			// Check if thermostat is currently Heating
625 			if (nshared["can_heat"].asBool() && !nshared["hvac_heater_state"].empty())
626 			{
627 				bool bIsHeating = nshared["hvac_heater_state"].asBool();
628 				UpdateSwitch((unsigned char)(113 + (iThermostat * 3)), bIsHeating, Name + " HeatingOn");
629 			}
630 
631 			// Check if thermostat is currently Cooling
632 			if (nshared["can_cool"].asBool() && !nshared["hvac_ac_state"].empty())
633 			{
634 				bool bIsCooling = nshared["hvac_ac_state"].asBool();
635 				UpdateSwitch((unsigned char)(114 + (iThermostat * 3)), bIsCooling, Name + " CoolingOn");
636 			}
637 
638 			//Away
639 			if (!nstructure["away"].empty())
640 			{
641 				bool bIsAway = nstructure["away"].asBool();
642 				SendSwitch((iThermostat * 3) + 3, 1, 255, bIsAway, 0, Name + " Away");
643 			}
644 
645 			//Manual Eco mode
646 			if (!ndevice["eco"]["mode"].empty())
647 			{
648 				std::string sCurrentHvacMode = ndevice["eco"]["mode"].asString();
649 				bool bIsManualEcoMode = (sCurrentHvacMode == "manual-eco");
650 				SendSwitch((iThermostat * 3) + 4, 1, 255, bIsManualEcoMode, 0, Name + " Manual Eco Mode");
651 			}
652 
653 			iThermostat++;
654 		}
655 	}
656 }
657 
SetSetpoint(const int idx,const float temp)658 void CNest::SetSetpoint(const int idx, const float temp)
659 {
660 	if (m_UserName.size() == 0)
661 		return;
662 	if (m_Password.size() == 0)
663 		return;
664 
665 	if (m_bDoLogin == true)
666 	{
667 		if (!Login())
668 			return;
669 	}
670 	size_t iThermostat = (idx - 1) / 3;
671 	if (iThermostat > m_thermostats.size())
672 		return;
673 
674 	std::vector<std::string> ExtraHeaders;
675 
676 	ExtraHeaders.push_back(NEST_USER_AGENT_STRING);
677 	ExtraHeaders.push_back("Authorization:Basic " + m_AccessToken);
678 	ExtraHeaders.push_back("X-nl-protocol-version:1");
679 
680 	float tempDest = temp;
681 	unsigned char tSign = m_sql.m_tempsign[0];
682 	if (tSign == 'F')
683 	{
684 		tempDest = static_cast<float>(ConvertToCelsius(tempDest));
685 	}
686 
687 	Json::Value root;
688 	root["target_change_pending"] = true;
689 	root["target_temperature"] = tempDest;
690 
691 	std::string sResult;
692 
693 	std::string sURL = m_TransportURL + NEST_SET_SHARED + m_thermostats[iThermostat].Serial;
694 	if (!HTTPClient::POST(sURL, root.toStyledString(), ExtraHeaders, sResult, true, true))
695 	{
696 		_log.Log(LOG_ERROR, "Nest: Error setting setpoint!");
697 		m_bDoLogin = true;
698 		return;
699 	}
700 	GetMeterDetails();
701 }
702 
SetAway(const unsigned char Idx,const bool bIsAway)703 bool CNest::SetAway(const unsigned char Idx, const bool bIsAway)
704 {
705 	if (m_UserName.size() == 0)
706 		return false;
707 	if (m_Password.size() == 0)
708 		return false;
709 
710 	if (m_bDoLogin == true)
711 	{
712 		if (!Login())
713 			return false;
714 	}
715 
716 	size_t iThermostat = (Idx - 3) / 3;
717 	if (iThermostat > m_thermostats.size())
718 		return false;
719 
720 	std::vector<std::string> ExtraHeaders;
721 
722 	ExtraHeaders.push_back(NEST_USER_AGENT_STRING);
723 	ExtraHeaders.push_back("Authorization:Basic " + m_AccessToken);
724 	ExtraHeaders.push_back("X-nl-protocol-version:1");
725 
726 	Json::Value root;
727 	root["away"] = bIsAway;
728 	root["away_timestamp"] = (int)mytime(NULL);
729 	root["away_setter"] = 0;
730 
731 	std::string sResult;
732 
733 	std::string sURL = m_TransportURL + NEST_SET_STRUCTURE + m_thermostats[iThermostat].StructureID;
734 	if (!HTTPClient::POST(sURL, root.toStyledString(), ExtraHeaders, sResult, true, true))
735 	{
736 		_log.Log(LOG_ERROR, "Nest: Error setting away mode!");
737 		m_bDoLogin = true;
738 		return false;
739 	}
740 	return true;
741 }
742 
SetManualEcoMode(const unsigned char Idx,const bool bIsManualEcoMode)743 bool CNest::SetManualEcoMode(const unsigned char Idx, const bool bIsManualEcoMode)
744 {
745 	if (m_UserName.size() == 0)
746 		return false;
747 	if (m_Password.size() == 0)
748 		return false;
749 
750 	if (m_bDoLogin == true)
751 	{
752 		if (!Login())
753 			return false;
754 	}
755 
756 	size_t iThermostat = (Idx - 4) / 3;
757 	if (iThermostat > m_thermostats.size())
758 		return false;
759 
760 	std::vector<std::string> ExtraHeaders;
761 
762 	ExtraHeaders.push_back(NEST_USER_AGENT_STRING);
763 	ExtraHeaders.push_back("Authorization:Basic " + m_AccessToken);
764 	ExtraHeaders.push_back("X-nl-protocol-version:1");
765 
766 	Json::Value root;
767 	Json::Value eco;
768 
769 	eco["mode"] = (bIsManualEcoMode ? "manual-eco" : "schedule");
770 	root["eco"] = eco;
771 
772 	std::string sResult;
773 
774 	// If thermostat information has not yet been read we can't do anything so let's fail.
775 	if (m_thermostats[iThermostat].Serial.empty())
776 		return false;
777 
778 	std::string sURL = m_TransportURL + NEST_SET_DEVICE + m_thermostats[iThermostat].Serial;
779 	if (!HTTPClient::POST(sURL, root.toStyledString(), ExtraHeaders, sResult, true, true))
780 	{
781 		_log.Log(LOG_ERROR, "Nest: Error setting manual eco mode!");
782 		m_bDoLogin = true;
783 		return false;
784 	}
785 	return true;
786 }
787 
SetProgramState(const int)788 void CNest::SetProgramState(const int /*newState*/)
789 {
790 	if (m_UserName.size() == 0)
791 		return;
792 	if (m_Password.size() == 0)
793 		return;
794 
795 	if (m_bDoLogin)
796 	{
797 		if (!Login())
798 			return;
799 	}
800 }
801