Skip to content

Commit fcfc0cb

Browse files
committed
- Implemented: Thermosmart Thermostat
- Implemented: Option for Dummy device to create Soil Moisture,Leaf Wetness, RGB Switch
1 parent ed72a1d commit fcfc0cb

31 files changed

+312
-28
lines changed

History.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ Version 2.xxx (xxxx 2015)
7070
- Implemented: Option to enter a description for each sensor/switch/scene
7171
- Changed: OpenZWave, Alarm/Notification mappings update (xs4)
7272
- Changed: Windows serial port Enumeration (now also supports windows 10)
73+
- Implemented: Thermosmart Thermostat
74+
- Implemented: Option for Dummy device to create Soil Moisture,Leaf Wetness, RGB Switch
7375

7476
Version 2.2563 (June 14th 2015)
7577
- Implemented: WebServer, native SSL and keep-alive, big thanks to chimit!!!!

hardware/AnnaThermostat.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
#include "../main/Logger.h"
55
#include "hardwaretypes.h"
66
#include "../main/localtime_r.h"
7-
#include "../json/json.h"
87
#include "../main/RFXtrx.h"
98
#include "../main/SQLHelper.h"
109
#include "../httpclient/HTTPClient.h"
1110
#include "../main/mainworker.h"
12-
#include "../json/json.h"
1311
#include "../hardware/openzwave/control_panel/tinyxml/tinyxml.h"
1412

1513
#define round(a) ( int ) ( a + .5 )

hardware/Dummy.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,38 @@ namespace http {
192192
m_sql.UpdateValue(HwdID, ID, 1, pTypeGeneral, sTypeCounterIncremental, 12, 255, 0, "0", devname);
193193
bCreated = true;
194194
break;
195+
case 15:
196+
//Soil Moisture
197+
{
198+
std::string rID = std::string(ID);
199+
padLeft(rID, 8, '0');
200+
m_sql.UpdateValue(HwdID, rID.c_str(), 1, pTypeGeneral, sTypeSoilMoisture, 12, 255, 3, devname);
201+
bCreated = true;
202+
}
203+
break;
204+
case 16:
205+
//Leaf Wetness
206+
{
207+
std::string rID = std::string(ID);
208+
padLeft(rID, 8, '0');
209+
m_sql.UpdateValue(HwdID, rID.c_str(), 1, pTypeGeneral, sTypeLeafWetness, 12, 255, 2, devname);
210+
bCreated = true;
211+
}
212+
break;
213+
case 17:
214+
//Thermostat Clock
215+
{
216+
std::string rID = std::string(ID);
217+
padLeft(rID, 8, '0');
218+
m_sql.UpdateValue(HwdID, rID.c_str(), 1, pTypeGeneral, sTypeZWaveClock, 12, 255, 0, "24:12:00", devname);
219+
bCreated = true;
220+
}
221+
break;
222+
case pTypeLimitlessLights:
223+
//RGB switch
224+
m_sql.UpdateValue(HwdID, ID, 1, pTypeLimitlessLights, sTypeLimitlessRGB, 12, 255, 1, devname);
225+
bCreated = true;
226+
break;
195227
case pTypeTEMP:
196228
m_sql.UpdateValue(HwdID, ID, 1, pTypeTEMP, sTypeTEMP1, 10, 255, 0, "0.0", devname);
197229
bCreated = true;

hardware/Thermosmart.cpp

Lines changed: 174 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
const std::string THERMOSMART_LOGIN_PATH = "https://api.thermosmart.com/login";
1717
const std::string THERMOSMART_AUTHORISE_PATH = "https://api.thermosmart.com/oauth2/authorize?response_type=code&client_id=client123&redirect_uri=http://clientapp.com/done";
1818
const std::string THERMOSMART_DECISION_PATH = "https://api.thermosmart.com/oauth2/authorize/decision";
19-
const std::string THERMOSMART_TOKEN_PATH = "https://api.thermosmart.com/oauth2/token";
20-
const std::string THERMOSMART_ACCESS_PATH = "https://api.thermosmart.com/thermostat";
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]";
2122

2223
#ifdef _DEBUG
2324
//#define DEBUG_ThermosmartThermostat
@@ -51,10 +52,21 @@ std::string ReadFile(std::string filename)
5152
}
5253
#endif
5354

54-
CThermosmart::CThermosmart(const int ID, const std::string &Username, const std::string &Password) :
55-
m_UserName(Username),
56-
m_Password(Password)
55+
CThermosmart::CThermosmart(const int ID, const std::string &Username, const std::string &Password)
5756
{
57+
std::vector<std::string> results;
58+
StringSplit(Username, "*;", results);
59+
if (results.size()<4)
60+
{
61+
_log.Log(LOG_ERROR, "Thermosmart: Invalid login credentials provided");
62+
return;
63+
}
64+
65+
m_UserName = results[0];
66+
m_Password = results[1];
67+
m_ClientID = results[2];
68+
m_ClientSecret = results[3];
69+
5870
m_HwdID=ID;
5971
Init();
6072
}
@@ -160,33 +172,113 @@ bool CThermosmart::Login()
160172
m_AccessToken = "";
161173
m_ThermostatID = "";
162174

175+
std::string sURL;
163176
std::stringstream sstr;
164177
sstr << "username=" << m_UserName << "&password=" << m_Password;
165178
std::string szPostdata=sstr.str();
166179
std::vector<std::string> ExtraHeaders;
167180
std::string sResult;
168181

169-
std::string sURL = THERMOSMART_LOGIN_PATH;
182+
//# 1. Login
183+
184+
sURL = THERMOSMART_LOGIN_PATH;
170185
if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult))
171-
{
172-
_log.Log(LOG_ERROR,"Thermosmart: Error login!");
173-
return false;
174-
}
186+
{
187+
_log.Log(LOG_ERROR,"Thermosmart: Error login!");
188+
return false;
189+
}
190+
/*
175191
#ifdef DEBUG_ThermosmartThermostat
176192
SaveString2Disk(sResult, "E:\\thermosmart1.txt");
177193
#endif
178-
194+
*/
195+
//# 2. Get Authorize Dialog
179196
sURL = THERMOSMART_AUTHORISE_PATH;
180-
stdreplace(sURL, "client123", "3de470ce1db15f92");
197+
stdreplace(sURL, "client123", m_ClientID);
181198
ExtraHeaders.clear();
182199
if (!HTTPClient::GET(sURL, sResult))
183200
{
184201
_log.Log(LOG_ERROR, "Thermosmart: Error login!");
185202
return false;
186203
}
204+
/*
187205
#ifdef DEBUG_ThermosmartThermostat
188206
SaveString2Disk(sResult, "E:\\thermosmart2.txt");
189207
#endif
208+
*/
209+
size_t tpos = sResult.find("value=");
210+
if (tpos == std::string::npos)
211+
{
212+
_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
213+
return false;
214+
}
215+
sResult = sResult.substr(tpos + 7);
216+
tpos = sResult.find("\">");
217+
if (tpos == std::string::npos)
218+
{
219+
_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
220+
return false;
221+
}
222+
std::string TID = sResult.substr(0, tpos);
223+
224+
//# 3. Authorize (read out transaction_id from the HTML form received in the previous step). transaction_id prevents from XSRF attacks.
225+
szPostdata = "transaction_id=" + TID;
226+
ExtraHeaders.clear();
227+
sURL = THERMOSMART_DECISION_PATH;
228+
if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult, false))
229+
{
230+
_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
231+
return false;
232+
}
233+
/*
234+
#ifdef DEBUG_ThermosmartThermostat
235+
SaveString2Disk(sResult, "E:\\thermosmart3.txt");
236+
#endif
237+
*/
238+
tpos = sResult.find("code=");
239+
if (tpos == std::string::npos)
240+
{
241+
_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
242+
return false;
243+
}
244+
std::string CODE = sResult.substr(tpos + 5);
245+
246+
//# 4. Exchange authorization code for Access token (read out the code from the previous response)
247+
szPostdata = "grant_type=authorization_code&code=" + CODE + "&redirect_uri=http://clientapp.com/done";
248+
sURL = THERMOSMART_TOKEN_PATH;
249+
250+
stdreplace(sURL, "username", m_ClientID);
251+
stdreplace(sURL, "password", m_ClientSecret);
252+
253+
if (!HTTPClient::POST(sURL, szPostdata, ExtraHeaders, sResult, false))
254+
{
255+
_log.Log(LOG_ERROR, "Thermosmart: Error login!, check username/password");
256+
return false;
257+
}
258+
/*
259+
#ifdef DEBUG_ThermosmartThermostat
260+
SaveString2Disk(sResult, "E:\\thermosmart4.txt");
261+
#endif
262+
*/
263+
Json::Value root;
264+
Json::Reader jReader;
265+
bool ret = jReader.parse(sResult, root);
266+
if (!ret)
267+
{
268+
_log.Log(LOG_ERROR, "Thermosmart: Invalid/no data received...");
269+
return false;
270+
}
271+
272+
if (root["access_token"].empty()||root["thermostat"].empty())
273+
{
274+
_log.Log(LOG_ERROR, "Thermosmart: No access granted, check username/password...");
275+
return false;
276+
}
277+
278+
m_AccessToken = root["access_token"].asString();
279+
m_ThermostatID = root["thermostat"].asString();
280+
281+
_log.Log(LOG_STATUS, "Thermosmart: Login successfull!...");
190282

191283
m_bDoLogin = false;
192284
return true;
@@ -227,13 +319,83 @@ void CThermosmart::GetMeterDetails()
227319
if (m_Password.size()==0)
228320
return;
229321
std::string sResult;
322+
/*
323+
sResult = ReadFile("E:\\thermosmart_getdata.txt");
324+
*/
230325
if (m_bDoLogin)
231326
{
232327
if (!Login())
328+
return;
329+
}
330+
std::string sURL = THERMOSMART_ACCESS_PATH;
331+
stdreplace(sURL, "[TID]", m_ThermostatID);
332+
stdreplace(sURL, "[access_token]", m_AccessToken);
333+
if (!HTTPClient::GET(sURL, sResult))
334+
{
335+
_log.Log(LOG_ERROR, "Thermosmart: Error getting thermostat data!");
336+
m_bDoLogin = true;
337+
return;
338+
}
339+
340+
#ifdef DEBUG_ThermosmartThermostat
341+
SaveString2Disk(sResult, "E:\\thermosmart_getdata.txt");
342+
#endif
343+
344+
Json::Value root;
345+
Json::Reader jReader;
346+
bool ret = jReader.parse(sResult, root);
347+
if (!ret)
348+
{
349+
_log.Log(LOG_ERROR, "Thermosmart: Invalid/no data received...");
350+
m_bDoLogin = true;
351+
return;
352+
}
353+
354+
if (root["target_temperature"].empty() || root["room_temperature"].empty())
355+
{
356+
_log.Log(LOG_ERROR, "Thermosmart: Invalid/no data received...");
357+
m_bDoLogin = true;
233358
return;
234359
}
360+
361+
float temperature;
362+
temperature = (float)root["target_temperature"].asFloat();
363+
SendSetPointSensor(1, temperature, "target temperature");
364+
365+
temperature = (float)root["room_temperature"].asFloat();
366+
SendTempSensor(2, 255, temperature, "room temperature");
367+
368+
if (!root["outside_temperature"].empty())
369+
{
370+
temperature = (float)root["outside_temperature"].asFloat();
371+
SendTempSensor(3, 255, temperature, "outside temperature");
372+
}
235373
}
236374

237375
void CThermosmart::SetSetpoint(const int idx, const float temp)
238376
{
377+
if (m_bDoLogin)
378+
{
379+
if (!Login())
380+
return;
381+
}
382+
383+
char szTemp[20];
384+
sprintf(szTemp, "%.1f", temp);
385+
std::string sTemp = szTemp;
386+
387+
std::string szPostdata = "target_temperature=" + sTemp;
388+
std::vector<std::string> ExtraHeaders;
389+
std::string sResult;
390+
391+
std::string sURL = THERMOSMART_SETPOINT_PATH;
392+
stdreplace(sURL, "[TID]", m_ThermostatID);
393+
stdreplace(sURL, "[access_token]", m_AccessToken);
394+
if (!HTTPClient::PUT(sURL, szPostdata, ExtraHeaders, sResult))
395+
{
396+
_log.Log(LOG_ERROR, "Thermosmart: Error setting thermostat data!");
397+
m_bDoLogin = true;
398+
return;
399+
}
400+
SendSetPointSensor(1, temp, "target temperature");
239401
}

hardware/Thermosmart.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class CThermosmart : public CDomoticzHardwareBase
1818

1919
std::string m_UserName;
2020
std::string m_Password;
21+
std::string m_ClientID;
22+
std::string m_ClientSecret;
2123
std::string m_AccessToken;
2224
std::string m_ThermostatID;
2325
volatile bool m_stoprequested;

httpclient/HTTPClient.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ bool HTTPClient::GETBinaryToFile(const std::string &url, const std::string &outp
159159
}
160160
}
161161

162-
bool HTTPClient::POSTBinary(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::vector<unsigned char> &response)
162+
bool HTTPClient::POSTBinary(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::vector<unsigned char> &response, const bool bFollowRedirect)
163163
{
164164
try
165165
{
@@ -171,6 +171,10 @@ bool HTTPClient::POSTBinary(const std::string &url, const std::string &postdata,
171171

172172
CURLcode res;
173173
SetGlobalOptions(curl);
174+
if (!bFollowRedirect)
175+
{
176+
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
177+
}
174178
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
175179
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
176180
curl_easy_setopt(curl, CURLOPT_POST, 1);
@@ -277,11 +281,11 @@ bool HTTPClient::GET(const std::string &url, const std::vector<std::string> &Ext
277281
return true;
278282
}
279283

280-
bool HTTPClient::POST(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::string &response)
284+
bool HTTPClient::POST(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::string &response, const bool bFollowRedirect)
281285
{
282286
response = "";
283287
std::vector<unsigned char> vHTTPResponse;
284-
if (!POSTBinary(url,postdata,ExtraHeaders, vHTTPResponse))
288+
if (!POSTBinary(url,postdata,ExtraHeaders, vHTTPResponse, bFollowRedirect))
285289
return false;
286290
if (vHTTPResponse.empty())
287291
return true; //empty response possible

httpclient/HTTPClient.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class HTTPClient
1313
static bool GETBinaryToFile(const std::string &url, const std::string &outputfile);
1414

1515
//POST functions, postdata looks like: "name=john&age=123&country=this"
16-
static bool POST(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::string &response);
17-
static bool POSTBinary(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::vector<unsigned char> &response);
16+
static bool POST(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::string &response, const bool bFollowRedirect=true);
17+
static bool POSTBinary(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::vector<unsigned char> &response, const bool bFollowRedirect = true);
1818

1919
//PUT functions, postdata looks like: "name=john&age=123&country=this"
2020
static bool PUT(const std::string &url, const std::string &postdata, const std::vector<std::string> &ExtraHeaders, std::string &response);

0 commit comments

Comments
 (0)