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