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