1 #include "stdafx.h"
2 #include "TeslaApi.h"
3 #include "VehicleApi.h"
4 #include "../../main/Logger.h"
5 #include "../../httpclient/UrlEncode.h"
6 #include "../../httpclient/HTTPClient.h"
7 #include "../../main/json_helper.h"
8 #include <sstream>
9 #include <iomanip>
10
11 // based on TESLA API as described on https://tesla-api.timdorr.com/
12 #define TESLA_URL "https://owner-api.teslamotors.com"
13 #define TESLA_API "/api/1/vehicles"
14 #define TESLA_API_COMMAND "/command/"
15 #define TESLA_API_REQUEST "/data_request/"
16 #define TESLA_API_AUTH "/oauth/token"
17
18 #define TESLA_CLIENT_ID "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384"
19 #define TESLA_CLIENT_SECRET "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3"
20
21 #define TLAPITIMEOUT (30)
22
CTeslaApi(const std::string username,const std::string password,const std::string vinnr)23 CTeslaApi::CTeslaApi(const std::string username, const std::string password, const std::string vinnr)
24 {
25 m_username = username;
26 m_password = password;
27 m_VIN = vinnr;
28 m_authtoken = "";
29 m_refreshtoken = "";
30 m_carid = 0;
31 m_carname = "";
32 }
33
~CTeslaApi()34 CTeslaApi::~CTeslaApi()
35 {
36 }
37
Login()38 bool CTeslaApi::Login()
39 {
40 _log.Log(LOG_NORM, "TeslaApi: Attempting login.");
41 if (GetAuthToken(m_username, m_password, false))
42 {
43 if(FindCarInAccount())
44 {
45 _log.Log(LOG_NORM, "TeslaApi: Login successful.");
46 return true;
47 }
48 }
49
50 _log.Log(LOG_NORM, "TeslaApi: Failed to login.");
51 m_authtoken = "";
52 m_refreshtoken = "";
53 return false;
54 }
55
RefreshLogin()56 bool CTeslaApi::RefreshLogin()
57 {
58 _log.Log(LOG_NORM, "TeslaApi: Refreshing login credentials.");
59 if (GetAuthToken("", "", true))
60 {
61 _log.Log(LOG_NORM, "TeslaApi: Refresh successful.");
62 return true;
63 }
64
65 _log.Log(LOG_ERROR, "TeslaApi: Failed to refresh login credentials.");
66 m_authtoken = "";
67 m_refreshtoken = "";
68 return false;
69 }
70
GetSleepInterval()71 int CTeslaApi::GetSleepInterval()
72 {
73 return 20;
74 }
75
GetAllData(tAllCarData & data)76 bool CTeslaApi::GetAllData(tAllCarData& data)
77 {
78 Json::Value reply;
79
80 if (GetData("vehicle_data", reply))
81 {
82 GetLocationData(reply["response"]["drive_state"], data.location);
83 GetChargeData(reply["response"]["charge_state"], data.charge);
84 GetClimateData(reply["response"]["climate_state"], data.climate);
85 return true;
86 }
87 return false;
88 }
89
GetLocationData(tLocationData & data)90 bool CTeslaApi::GetLocationData(tLocationData& data)
91 {
92 Json::Value reply;
93
94 if (GetData("drive_state", reply))
95 {
96 GetLocationData(reply["response"], data);
97 return true;
98 }
99 return false;
100 }
101
GetLocationData(Json::Value & jsondata,tLocationData & data)102 void CTeslaApi::GetLocationData(Json::Value& jsondata, tLocationData& data)
103 {
104 std::string CarLatitude = jsondata["latitude"].asString();
105 std::string CarLongitude = jsondata["longitude"].asString();
106 data.speed = jsondata["speed"].asInt();
107 data.is_driving = data.speed > 0;
108 data.latitude = std::stod(CarLatitude);
109 data.longitude = std::stod(CarLongitude);
110 }
111
GetChargeData(CVehicleApi::tChargeData & data)112 bool CTeslaApi::GetChargeData(CVehicleApi::tChargeData& data)
113 {
114 Json::Value reply;
115
116 if (GetData("charge_state", reply))
117 {
118 GetChargeData(reply["response"], data);
119 return true;
120 }
121 return false;
122 }
123
GetChargeData(Json::Value & jsondata,CVehicleApi::tChargeData & data)124 void CTeslaApi::GetChargeData(Json::Value& jsondata, CVehicleApi::tChargeData& data)
125 {
126 data.battery_level = jsondata["battery_level"].asFloat();
127 data.status_string = jsondata["charging_state"].asString();
128 data.is_connected = (data.status_string != "Disconnected");
129 data.is_charging = (data.status_string == "Charging") || (data.status_string == "Starting");
130
131 if(data.status_string == "Disconnected")
132 data.status_string = "Charge Cable Disconnected";
133 }
134
GetClimateData(tClimateData & data)135 bool CTeslaApi::GetClimateData(tClimateData& data)
136 {
137 Json::Value reply;
138
139 if (GetData("climate_state", reply))
140 {
141 GetClimateData(reply["response"], data);
142 return true;
143 }
144 return false;
145 }
146
GetClimateData(Json::Value & jsondata,tClimateData & data)147 void CTeslaApi::GetClimateData(Json::Value& jsondata, tClimateData& data)
148 {
149 data.inside_temp = jsondata["inside_temp"].asFloat();
150 data.outside_temp = jsondata["outside_temp"].asFloat();
151 data.is_climate_on = jsondata["is_climate_on"].asBool();
152 data.is_defrost_on = (jsondata["defrost_mode"].asInt() != 0);
153 }
154
GetData(std::string datatype,Json::Value & reply)155 bool CTeslaApi::GetData(std::string datatype, Json::Value& reply)
156 {
157 std::stringstream ss;
158 if(datatype == "vehicle_data")
159 ss << TESLA_URL << TESLA_API << "/" << m_carid << "/" << datatype;
160 else
161 ss << TESLA_URL << TESLA_API << "/" << m_carid << TESLA_API_REQUEST << datatype;
162 std::string _sUrl = ss.str();
163 std::string _sResponse;
164
165 if (!SendToApi(Get, _sUrl, "", _sResponse, *(new std::vector<std::string>()), reply, true))
166 {
167 _log.Log(LOG_ERROR, "TeslaApi: Failed to get data %s.", datatype.c_str());
168 return false;
169 }
170
171 //_log.Log(LOG_NORM, "TeslaApi: Get data %s received reply: %s", datatype.c_str(), _sResponse.c_str());
172 _log.Debug(DEBUG_NORM, "TeslaApi: Get data %s received reply: %s", datatype.c_str(), _sResponse.c_str());
173
174 return true;
175 }
176
FindCarInAccount()177 bool CTeslaApi::FindCarInAccount()
178 {
179 std::stringstream ss;
180 ss << TESLA_URL << TESLA_API;
181 std::string _sUrl = ss.str();
182 std::string _sResponse;
183 Json::Value _jsRoot;
184 bool car_found = false;
185
186 if (!SendToApi(Get, _sUrl, "", _sResponse, *(new std::vector<std::string>()), _jsRoot, true))
187 {
188 _log.Log(LOG_ERROR, "TeslaApi: Failed to get car from account.");
189 return false;
190 }
191
192 _log.Debug(DEBUG_NORM, "TeslaApi: Received %d vehicles from API: %s", _jsRoot["count"].asInt(), _sResponse.c_str());
193 for (int i = 0; i < _jsRoot["count"].asInt(); i++)
194 {
195 if (_jsRoot["response"][i]["vin"].asString() == m_VIN)
196 {
197 m_carid = _jsRoot["response"][i]["id"].asInt64();
198 m_carname = _jsRoot["response"][i]["display_name"].asString();
199 car_found = true;
200 _log.Log(LOG_NORM, "TeslaApi: Car found in account: VIN %s NAME %s", m_VIN.c_str(), m_carname.c_str());
201 return true;
202 }
203 }
204
205 _log.Log(LOG_ERROR, "TeslaApi: Car with VIN number %s NOT found in account.", m_VIN.c_str());
206 return car_found;
207 }
208
IsAwake()209 bool CTeslaApi::IsAwake()
210 {
211 std::stringstream ss;
212 ss << TESLA_URL << TESLA_API << "/" << m_carid;
213 std::string _sUrl = ss.str();
214 std::string _sResponse;
215 Json::Value _jsRoot;
216 bool is_awake = false;
217
218 if (SendToApi(Get, _sUrl, "", _sResponse, *(new std::vector<std::string>()), _jsRoot, true))
219 {
220 //_log.Log(LOG_NORM, "Awake state: %s", _jsRoot["response"]["state"].asString().c_str());
221 _log.Debug(DEBUG_NORM, "Awake state: %s", _jsRoot["response"]["state"].asString().c_str());
222 is_awake = (_jsRoot["response"]["state"] == "online");
223 return(is_awake);
224 }
225
226 _log.Log(LOG_ERROR, "TeslaApi: Failed to get awake state.");
227 return false;
228 }
229
SendCommand(eCommandType command)230 bool CTeslaApi::SendCommand(eCommandType command)
231 {
232 std::string command_string;
233 Json::Value reply;
234 std::string parameters = "";
235
236 switch (command)
237 {
238 case Charge_Start:
239 command_string = "charge_start";
240 break;
241 case Charge_Stop:
242 command_string = "charge_stop";
243 break;
244 case Climate_Off:
245 command_string = "auto_conditioning_stop";
246 break;
247 case Climate_On:
248 command_string = "auto_conditioning_start";
249 break;
250 case Climate_Defrost:
251 command_string = "set_preconditioning_max";
252 parameters = "on=true";
253 break;
254 case Climate_Defrost_Off:
255 command_string = "set_preconditioning_max";
256 parameters = "on=false";
257 break;
258 case Wake_Up:
259 command_string = "wake_up";
260 break;
261
262 }
263
264 if (SendCommand(command_string, reply, parameters))
265 {
266 if (command == Wake_Up)
267 {
268 if (reply["response"]["state"].asString() == "online")
269 return true;
270 }
271 else
272 {
273 if (reply["response"]["result"].asString() == "true")
274 return true;
275 }
276 }
277
278 return false;
279 }
280
SendCommand(std::string command,Json::Value & reply,std::string parameters)281 bool CTeslaApi::SendCommand(std::string command, Json::Value& reply, std::string parameters)
282 {
283 std::stringstream ss;
284 if (command == "wake_up")
285 ss << TESLA_URL << TESLA_API << "/" << m_carid << "/" << command;
286 else
287 ss << TESLA_URL << TESLA_API << "/" << m_carid << TESLA_API_COMMAND << command;
288
289 std::string _sUrl = ss.str();
290 std::string _sResponse;
291
292 std::stringstream parss;
293 parss << parameters;
294 std::string sPostData = parss.str();
295
296 if (!SendToApi(Post, _sUrl, sPostData, _sResponse, *(new std::vector<std::string>()), reply, true))
297 {
298 _log.Log(LOG_ERROR, "TeslaApi: Failed to send command %s.", command.c_str());
299 return false;
300 }
301
302 // _log.Log(LOG_NORM, "TeslaApi: Command %s received reply: %s", GetCommandString(command).c_str(), _sResponse.c_str());
303 _log.Debug(DEBUG_NORM, "TeslaApi: Command %s received reply: %s", command.c_str(), _sResponse.c_str());
304
305 return true;
306 }
307
308 // Requests an authentication token from the Tesla OAuth Api.
GetAuthToken(const std::string username,const std::string password,const bool refreshUsingToken)309 bool CTeslaApi::GetAuthToken(const std::string username, const std::string password, const bool refreshUsingToken)
310 {
311 if (username.size() == 0 && !refreshUsingToken)
312 {
313 _log.Log(LOG_ERROR, "TeslaApi: No username specified.");
314 return false;
315 }
316 if (username.size() == 0 && !refreshUsingToken)
317 {
318 _log.Log(LOG_ERROR, "TeslaApi: No password specified.");
319 return false;
320 }
321
322 std::stringstream ss;
323 ss << TESLA_URL << TESLA_API_AUTH;
324 std::string _sUrl = ss.str();
325 std::ostringstream s;
326 std::string _sGrantType = (refreshUsingToken ? "refresh_token" : "password");
327
328 s << "client_id=" << TESLA_CLIENT_ID << "&grant_type=";
329 s << _sGrantType << "&client_secret=";
330 s << TESLA_CLIENT_SECRET;
331
332 if (refreshUsingToken)
333 {
334 s << "&refresh_token=" << m_refreshtoken;
335 }
336 else
337 {
338 s << "&password=" << CURLEncode::URLEncode(password);
339 s << "&email=" << CURLEncode::URLEncode(username);
340 }
341
342 std::string sPostData = s.str();
343
344 std::string _sResponse;
345 std::vector<std::string> _vExtraHeaders;
346 _vExtraHeaders.push_back("Content-Type: application/x-www-form-urlencoded");
347
348 Json::Value _jsRoot;
349
350 if (!SendToApi(Post, _sUrl, sPostData, _sResponse, _vExtraHeaders, _jsRoot, false))
351 {
352 _log.Log(LOG_ERROR, "TeslaApi: Failed to get token.");
353 return false;
354 }
355
356 m_authtoken = _jsRoot["access_token"].asString();
357 if (m_authtoken.size() == 0)
358 {
359 _log.Log(LOG_ERROR, "TeslaApi: Received token is zero length.");
360 return false;
361 }
362
363 m_refreshtoken = _jsRoot["refresh_token"].asString();
364 if (m_refreshtoken.size() == 0)
365 {
366 _log.Log(LOG_ERROR, "TeslaApi: Received refresh token is zero length.");
367 return false;
368 }
369
370 _log.Debug(DEBUG_NORM, "TeslaApi: Received access token from API.");
371
372 return true;
373 }
374
375 // Sends a request to the Tesla API.
SendToApi(const eApiMethod eMethod,const std::string & sUrl,const std::string & sPostData,std::string & sResponse,const std::vector<std::string> & vExtraHeaders,Json::Value & jsDecodedResponse,const bool bSendAuthHeaders)376 bool CTeslaApi::SendToApi(const eApiMethod eMethod, const std::string& sUrl, const std::string& sPostData,
377 std::string& sResponse, const std::vector<std::string>& vExtraHeaders, Json::Value& jsDecodedResponse, const bool bSendAuthHeaders)
378 {
379 try
380 {
381 // If there is no token stored then there is no point in doing a request. Unless we specifically
382 // decide not to do authentication.
383 if (m_authtoken.size() == 0 && bSendAuthHeaders)
384 {
385 _log.Log(LOG_ERROR, "TeslaApi: No access token available.");
386 return false;
387 }
388
389 // Prepare the headers. Copy supplied vector.
390 std::vector<std::string> _vExtraHeaders = vExtraHeaders;
391
392 // If the supplied postdata validates as json, add an appropriate content type header
393 if (sPostData.size() > 0)
394 if (ParseJSon(sPostData, *(new Json::Value)))
395 _vExtraHeaders.push_back("Content-Type: application/json");
396
397 // Prepare the authentication headers if requested.
398 if (bSendAuthHeaders)
399 _vExtraHeaders.push_back("Authorization: Bearer " + m_authtoken);
400
401 // Increase default timeout, tesla is slow
402 HTTPClient::SetConnectionTimeout(TLAPITIMEOUT);
403 HTTPClient::SetTimeout(TLAPITIMEOUT);
404
405 std::vector<std::string> _vResponseHeaders;
406 std::stringstream _ssResponseHeaderString;
407
408 switch (eMethod)
409 {
410 case Post:
411 if (!HTTPClient::POST(sUrl, sPostData, _vExtraHeaders, sResponse, _vResponseHeaders))
412 {
413 for (unsigned int i = 0; i < _vResponseHeaders.size(); i++)
414 _ssResponseHeaderString << _vResponseHeaders[i];
415 _log.Debug(DEBUG_NORM, "TeslaApi: Failed to perform POST request to Api: %s; Response headers: %s", sResponse.c_str(), _ssResponseHeaderString.str().c_str());
416 return false;
417 }
418 break;
419
420 case Get:
421 if (!HTTPClient::GET(sUrl, _vExtraHeaders, sResponse, _vResponseHeaders))
422 {
423 for (unsigned int i = 0; i < _vResponseHeaders.size(); i++)
424 _ssResponseHeaderString << _vResponseHeaders[i];
425 _log.Debug(DEBUG_NORM, "TeslaApi: Failed to perform GET request to Api: %s; Response headers: %s", sResponse.c_str(), _ssResponseHeaderString.str().c_str());
426 return false;
427 }
428 break;
429
430 default:
431 {
432 _log.Log(LOG_ERROR, "TeslaApi: Unknown method specified.");
433 return false;
434 }
435 }
436
437 if (sResponse.size() == 0)
438 {
439 _log.Log(LOG_ERROR, "TeslaApi: Received an empty response from Api.");
440 return false;
441 }
442
443 if (!ParseJSon(sResponse, jsDecodedResponse))
444 {
445 _log.Log(LOG_ERROR, "TeslaApi: Failed to decode Json response from Api.");
446 return false;
447 }
448 }
449 catch (std::exception & e)
450 {
451 std::string what = e.what();
452 _log.Log(LOG_ERROR, "TeslaApi: Error sending information to Api: %s", what.c_str());
453 return false;
454 }
455 return true;
456 }
457