1 #include "stdafx.h"
2 #include "eVehicle.h"
3 #include "TeslaApi.h"
4 #include "../../main/Helper.h"
5 #include "../../main/Logger.h"
6 #include "../hardwaretypes.h"
7 #include "../../main/localtime_r.h"
8 #include "../../main/RFXtrx.h"
9 #include "../../main/SQLHelper.h"
10 #include <sstream>
11 #include <iomanip>
12 #include <cmath>
13 
14 #define VEHICLE_SWITCH_CHARGE 1
15 #define VEHICLE_SWITCH_CLIMATE 2
16 #define VEHICLE_SWITCH_DEFROST 3
17 
18 #define VEHICLE_TEMP_INSIDE 1
19 #define VEHICLE_TEMP_OUTSIDE 2
20 #define VEHICLE_ALERT_STATUS 1
21 #define VEHICLE_LEVEL_BATTERY 1
22 
23 #define VEHICLE_MAXTRIES 5
24 
CeVehicle(const int ID,eVehicleType vehicletype,const std::string & username,const std::string & password,int defaultinterval,int activeinterval,bool allowwakeup,const std::string & carid)25 CeVehicle::CeVehicle(const int ID, eVehicleType vehicletype, const std::string& username, const std::string& password, int defaultinterval, int activeinterval, bool allowwakeup, const std::string& carid)
26 {
27 	m_loggedin = false;
28 	m_HwdID = ID;
29 	Init();
30 	m_commands.clear();
31 	m_currentalert = Sleeping;
32 	m_currentalerttext = "";
33 
34 	switch (vehicletype)
35 	{
36 	case Tesla:
37 		m_api = new CTeslaApi(username, password, carid);
38 		break;
39 	default:
40 		Log(LOG_ERROR, "Unsupported vehicle type.");
41 		m_api = nullptr;
42 		break;
43 	}
44 	if (defaultinterval > 0)
45 	{
46 		m_defaultinterval = defaultinterval;
47 		if(m_defaultinterval < m_api->GetSleepInterval())
48 			Log(LOG_ERROR, "Warning: default interval of %d minutes will prevent the car to sleep.", m_defaultinterval);
49 	}
50 	else
51 		m_defaultinterval = m_api->GetSleepInterval();
52 
53 	if (activeinterval > 0)
54 		m_activeinterval = activeinterval;
55 	else
56 		m_activeinterval = 1;
57 
58 	m_allowwakeup = allowwakeup;
59 }
60 
~CeVehicle(void)61 CeVehicle::~CeVehicle(void)
62 {
63 	m_commands.clear();
64 	delete m_api;
65 }
66 
Init()67 void CeVehicle::Init()
68 {
69 	m_car.charging = false;
70 	m_car.connected = false;
71 	m_car.is_home = false;
72 	m_car.climate_on = false;
73 	m_car.defrost = false;
74 	m_car.wake_state = Asleep;
75 	m_car.charge_state = "";
76 	m_command_nr_tries = 0;
77 	m_setcommand_scheduled = false;
78 }
79 
SendAlert()80 void CeVehicle::SendAlert()
81 {
82 	eAlertType alert;
83 	std::string title;
84 
85 	if (m_car.is_home && (m_car.wake_state != Asleep))
86 	{
87 		if (m_car.charging)
88 		{
89 			alert = Charging;
90 			title = "Home";
91 		}
92 		else if (m_car.connected)
93 		{
94 			alert = NotCharging;
95 			title = "Home";
96 		}
97 		else
98 		{
99 			alert = Idling;
100 			if(m_car.wake_state == WakingUp)
101 				title = "Waking Up";
102 			else
103 				title = "Home";
104 		}
105 		if (!m_car.charge_state.empty())
106 			title = title + ", " + m_car.charge_state;
107 	}
108 	else if (m_car.wake_state == Asleep)
109 	{
110 		alert = Sleeping;
111 		if (m_command_nr_tries > VEHICLE_MAXTRIES)
112 			title = "Offline";
113 		else
114 			title = "Asleep";
115 	}
116 	else
117 	{
118 		alert = NotHome;
119 		if (m_car.wake_state == WakingUp)
120 			title = "Waking Up";
121 		else
122 			title = "Not Home";
123 		if (m_car.charging && !m_car.charge_state.empty())
124 			title = title + ", " + m_car.charge_state;
125 	}
126 
127 	if((alert != m_currentalert) || (title != m_currentalerttext))
128 	{
129 		SendAlertSensor(VEHICLE_ALERT_STATUS, 255, alert, title, m_Name + " State");
130 		m_currentalert = alert;
131 		m_currentalerttext = title;
132 	}
133 }
134 
135 
ConditionalReturn(bool commandOK,eApiCommandType command)136 bool CeVehicle::ConditionalReturn(bool commandOK, eApiCommandType command)
137 {
138 	if(commandOK)
139 	{
140 		m_command_nr_tries = 0;
141 		SendAlert();
142 		return true;
143 	}
144 	else if(m_command_nr_tries > VEHICLE_MAXTRIES)
145 	{
146 		Init();
147 		SendSwitch(VEHICLE_SWITCH_CHARGE, 1, 255, m_car.charging, 0, m_Name + " Charge switch");
148 		SendSwitch(VEHICLE_SWITCH_CLIMATE, 1, 255, m_car.climate_on, 0, m_Name + " Climate switch");
149 		SendSwitch(VEHICLE_SWITCH_DEFROST, 1, 255, m_car.defrost, 0, m_Name + " Defrost switch");
150 		m_commands.clear();
151 		Log(LOG_ERROR, "Multiple tries requesting %s. Assuming car offline.", GetCommandString(command).c_str());
152 		SendAlert();
153 		return(false);
154 	}
155 	else
156 	{
157 		if (command == Wake_Up)
158 		{
159 			Log(LOG_ERROR, "Car not yet awake. Will retry.");
160 			SendAlert();
161 		}
162 		else
163 			Log(LOG_ERROR, "Timeout requesting %s. Will retry.", GetCommandString(command).c_str());
164 		m_command_nr_tries++;
165 	}
166 
167 	return(true);
168 }
169 
StartHardware()170 bool CeVehicle::StartHardware()
171 {
172 	RequestStart();
173 
174 	Init();
175 	//Start worker thread
176 	m_thread = std::make_shared<std::thread>(&CeVehicle::Do_Work, this);
177 	SetThreadNameInt(m_thread->native_handle());
178 	if (!m_thread)
179 		return false;
180 	m_bIsStarted = true;
181 	sOnConnected(this);
182 	return true;
183 }
184 
StopHardware()185 bool CeVehicle::StopHardware()
186 {
187 	if (m_thread)
188 	{
189 		RequestStop();
190 		m_thread->join();
191 		m_thread.reset();
192 	}
193 	m_bIsStarted = false;
194 	return true;
195 }
196 
GetCommandString(const eApiCommandType command)197 std::string CeVehicle::GetCommandString(const eApiCommandType command)
198 {
199 	switch (command)
200 	{
201 	case Send_Climate_Off:
202 		return("Switch Climate off");
203 	case Send_Climate_On:
204 		return("Switch Climate on");
205 	case Send_Climate_Defrost:
206 		return("Switch Defrost mode on");
207 	case Send_Climate_Defrost_Off:
208 		return("Switch Defrost mode off");
209 	case Send_Charge_Start:
210 		return("Start charging");
211 	case Send_Charge_Stop:
212 		return("Stop charging");
213 	case Get_All_States:
214 		return("Get All states");
215 	case Get_Climate_State:
216 		return("Get Climate state");
217 	case Get_Charge_State:
218 		return("Get Charge state");
219 	case Get_Location_State:
220 		return("Get Location state");
221 	case Wake_Up:
222 		return("Wake Up");
223 	case Get_Awake_State:
224 		return("Get Awake state");
225 	default:
226 		return "";
227 	}
228 }
229 
Do_Work()230 void CeVehicle::Do_Work()
231 {
232 	int sec_counter = 0;
233 	int interval = 1000;
234 	bool initial_check = true;
235 	Log(LOG_STATUS, "Worker started...");
236 
237 	while (!IsStopRequested(interval))
238 	{
239 		interval = 1000;
240 		sec_counter++;
241 		time_t now = mytime(0);
242 		m_LastHeartbeat = now;
243 
244 		if (m_api == nullptr)
245 			break;
246 
247 		// Only login if we should
248 		if (!m_loggedin || (sec_counter % 68400 == 0))
249 		{
250 			Login();
251 			sec_counter = 1;
252 			continue;
253 		}
254 
255 		// if commands scheduled, wake up car if needed and execute commands
256 		if (!m_commands.empty())
257 		{
258 			if (IsAwake())
259 			{
260 				if(DoNextCommand())
261 				{
262 					// if failed try (e.g. timeout), wait a while
263 					if (m_command_nr_tries > 0)
264 					{
265 						interval = 5000;
266 					}
267 				}
268 			}
269 			else
270 			{
271 				// car should wake up first, if allowed
272 				if (m_allowwakeup || m_car.charging || m_setcommand_scheduled)
273 				{
274 					if (WakeUp())
275 						if(m_car.wake_state == Awake)
276 							interval = 5000;
277 				}
278 				else
279 				{
280 					eApiCommandType item;
281 					m_commands.try_pop(item);
282 					Log(LOG_STATUS, "Car asleep, not allowed to wake up, command %s ignored.", GetCommandString(item).c_str());
283 				}
284 			}
285 		}
286 		else
287 		{
288 			m_setcommand_scheduled = false;
289 		}
290 
291 		// now do wake state checks
292 		if (initial_check)
293 		{
294 			if(IsAwake() || m_allowwakeup)
295 				m_commands.push(Get_All_States);
296 			initial_check = false;
297 		}
298 		else if ((sec_counter % 60) == 0)
299 		{
300 			// check awake state every minute
301 			if (IsAwake())
302 			{
303 				if (!m_allowwakeup && m_car.wake_state == SelfAwake)
304 				{
305 					Log(LOG_STATUS, "Spontaneous wake up detected.");
306 					m_commands.push(Get_All_States);
307 				}
308 			}
309 		}
310 
311 		// now schedule timed commands
312 		if ((sec_counter % (60*m_defaultinterval) == 0))
313 		{
314 			// check all states every default interval
315 			m_commands.push(Get_All_States);
316 		}
317 		else if (sec_counter % (60*m_activeinterval) == 0)
318 		{
319 			if (m_car.is_home && (m_car.charging || m_car.climate_on || m_car.defrost))
320 			{
321 				// check relevant states every active interval
322 				if (m_car.charging)
323 					m_commands.push(Get_Charge_State);
324 				if (m_car.climate_on || m_car.defrost)
325 					m_commands.push(Get_Climate_State);
326 			}
327 		}
328 		else
329 		{
330 			;
331 		}
332 	}
333 
334 	Log(LOG_STATUS, "Worker stopped...");
335 }
336 
WriteToHardware(const char * pdata,const unsigned char length)337 bool CeVehicle::WriteToHardware(const char* pdata, const unsigned char length)
338 {
339 	if (!m_loggedin)
340 		return false;
341 
342 	const tRBUF* pCmd = reinterpret_cast<const tRBUF*>(pdata);
343 	if (pCmd->LIGHTING2.packettype != pTypeLighting2)
344 		return false;
345 
346 	bool bIsOn = (pCmd->LIGHTING2.cmnd == light2_sOn);
347 
348 	m_commands.push(Get_Location_State);
349 	m_setcommand_scheduled = true;
350 	switch (pCmd->LIGHTING2.id4)
351 	{
352 	case VEHICLE_SWITCH_CHARGE:
353 		m_commands.push(Get_Charge_State);
354 		if (bIsOn)
355 			m_commands.push(Send_Charge_Start);
356 		else
357 			m_commands.push(Send_Charge_Stop);
358 		break;
359 	case VEHICLE_SWITCH_CLIMATE:
360 		if (bIsOn)
361 			m_commands.push(Send_Climate_On);
362 		else
363 			m_commands.push(Send_Climate_Off);
364 		break;
365 	case VEHICLE_SWITCH_DEFROST:
366 		if (bIsOn)
367 			m_commands.push(Send_Climate_Defrost);
368 		else
369 			m_commands.push(Send_Climate_Defrost_Off);
370 		break;
371 	default:
372 		Log(LOG_ERROR, "Unknown switch %d", pCmd->LIGHTING2.id4);
373 		return false;
374 	}
375 
376 	Debug(DEBUG_NORM, "Write to hardware called: on: %d", bIsOn);
377 	return true;
378 }
379 
Login()380 void CeVehicle::Login()
381 {
382 	// Only login if we should.
383 	if (!m_loggedin)
384 	{
385 		m_loggedin = m_api->Login();
386 	}
387 	else
388 		m_loggedin = m_api->RefreshLogin();
389 }
390 
391 
IsAwake()392 bool CeVehicle::IsAwake()
393 {
394 	Log(LOG_STATUS, "Executing command: %s", GetCommandString(Get_Awake_State).c_str());
395 
396 	if (m_api->IsAwake())
397 	{
398 		if (m_car.wake_state == Asleep)
399 			m_car.wake_state = SelfAwake;
400 		else if (m_car.wake_state == WakingUp)
401 			m_car.wake_state = Awake;
402 		else if (m_car.wake_state == SelfAwake)
403 			m_car.wake_state = Awake;
404 		Log(LOG_NORM, "Car is awake");
405 		return true;
406 	}
407 	else
408 	{
409 		if (m_car.wake_state == Awake)
410 			m_car.wake_state = Asleep;
411 		else if (m_car.wake_state == SelfAwake)
412 			m_car.wake_state = Asleep;
413 		Log(LOG_NORM, "Car is asleep");
414 		SendAlert();
415 	}
416 	return false;
417 }
418 
WakeUp()419 bool CeVehicle::WakeUp()
420 {
421 	if (m_command_nr_tries == 0)
422 	{
423 		Log(LOG_STATUS, "Waking up car.");
424 		m_car.wake_state = WakingUp;
425 	}
426 
427 	if (m_api->SendCommand(CVehicleApi::Wake_Up))
428 	{
429 		m_car.wake_state = Awake;
430 	}
431 
432 	return ConditionalReturn(m_car.wake_state == Awake, Wake_Up);
433 }
434 
DoNextCommand()435 bool CeVehicle::DoNextCommand()
436 {
437 	eApiCommandType command;
438 	m_commands.try_pop(command);
439 	bool commandOK = false;
440 
441 	Log(LOG_STATUS, "Executing command: %s", GetCommandString(command).c_str());
442 
443 	switch (command)
444 	{
445 	case Send_Climate_Off:
446 	case Send_Climate_On:
447 	case Send_Climate_Defrost:
448 	case Send_Climate_Defrost_Off:
449 	case Send_Charge_Start:
450 	case Send_Charge_Stop:
451 		commandOK = DoSetCommand(command);
452 		break;
453 	case Get_All_States:
454 		commandOK = GetAllStates();
455 		break;
456 	case Get_Climate_State:
457 		commandOK = GetClimateState();
458 		break;
459 	case Get_Charge_State:
460 		commandOK = GetChargeState();
461 		break;
462 	case Get_Location_State:
463 		commandOK = GetLocationState();
464 		break;
465 	default:
466 		commandOK = false;
467 	}
468 
469 	// if failed try (e.g. timeout), reschedule command
470 	if (commandOK && m_command_nr_tries > 0)
471 	{
472 		m_commands.push(command);
473 	}
474 
475 	return commandOK;
476 }
477 
DoSetCommand(eApiCommandType command)478 bool CeVehicle::DoSetCommand(eApiCommandType command)
479 {
480 	CVehicleApi::eCommandType api_command;
481 
482 	if (!m_car.is_home)
483 	{
484 		Log(LOG_ERROR, "Car not home. No commands allowed.");
485 		SendSwitch(VEHICLE_SWITCH_CHARGE, 1, 255, m_car.charging, 0, m_Name + " Charge switch");
486 		SendSwitch(VEHICLE_SWITCH_CLIMATE, 1, 255, m_car.climate_on, 0, m_Name + " Climate switch");
487 		SendSwitch(VEHICLE_SWITCH_DEFROST, 1, 255, m_car.defrost, 0, m_Name + " Defrost switch");
488 		return true;
489 	}
490 
491 	switch (command)
492 	{
493 	case Send_Charge_Start:
494 		if (!m_car.connected)
495 		{
496 			Log(LOG_ERROR, "Charge cable not connected. No charge commands possible.");
497 			SendSwitch(VEHICLE_SWITCH_CHARGE, 1, 255, m_car.charging, 0, m_Name + " Charge switch");
498 			return false;
499 		}
500 		api_command = CVehicleApi::Charge_Start;
501 		break;
502 	case Send_Charge_Stop:
503 		if (!m_car.connected)
504 		{
505 			Log(LOG_ERROR, "Charge cable not connected. No charge commands possible.");
506 			SendSwitch(VEHICLE_SWITCH_CHARGE, 1, 255, m_car.charging, 0, m_Name + " Charge switch");
507 			return false;
508 		}
509 		api_command = CVehicleApi::Charge_Stop;
510 		break;
511 	case Send_Climate_Off:
512 		api_command = CVehicleApi::Climate_Off;
513 		break;
514 	case Send_Climate_On:
515 		api_command = CVehicleApi::Climate_On;
516 		break;
517 	case Send_Climate_Defrost:
518 		api_command = CVehicleApi::Climate_Defrost;
519 		break;
520 	case Send_Climate_Defrost_Off:
521 		api_command = CVehicleApi::Climate_Defrost_Off;
522 		break;
523 	}
524 
525 	if (m_api->SendCommand(api_command))
526 	{
527 		switch (command)
528 		{
529 		case Send_Charge_Start:
530 		case Send_Charge_Stop:
531 			m_commands.push(Get_Charge_State);
532 			return true;
533 		case Send_Climate_Off:
534 		case Send_Climate_On:
535 			m_commands.push(Get_Climate_State);
536 			return true;
537 		case Send_Climate_Defrost:
538 		case Send_Climate_Defrost_Off:
539 			m_commands.push(Get_Climate_State);
540 			return ConditionalReturn(true, command);
541 		}
542 	}
543 
544 	return ConditionalReturn(false, command);
545 }
546 
GetAllStates()547 bool CeVehicle::GetAllStates()
548 {
549 	CVehicleApi::tAllCarData reply;
550 
551 	if (m_api->GetAllData(reply))
552 	{
553 		UpdateLocationData(reply.location);
554 		UpdateChargeData(reply.charge);
555 		UpdateClimateData(reply.climate);
556 		return ConditionalReturn(true, Get_All_States);
557 	}
558 
559 	return ConditionalReturn(false, Get_All_States);
560 }
561 
GetLocationState()562 bool CeVehicle::GetLocationState()
563 {
564 	CVehicleApi::tLocationData reply;
565 
566 	if (m_api->GetLocationData(reply))
567 	{
568 		UpdateLocationData(reply);
569 		return ConditionalReturn(true, Get_Location_State);
570 	}
571 
572 	return ConditionalReturn(false, Get_Location_State);
573 }
574 
UpdateLocationData(CVehicleApi::tLocationData & data)575 void CeVehicle::UpdateLocationData(CVehicleApi::tLocationData& data)
576 {
577 	bool car_old_state = m_car.is_home;
578 	int nValue;
579 	std::string sValue;
580 	std::vector<std::string> strarray;
581 	if (m_sql.GetPreferencesVar("Location", nValue, sValue))
582 		StringSplit(sValue, ";", strarray);
583 
584 	if (strarray.size() != 2)
585 	{
586 		Log(LOG_ERROR, "No location set in Domoticz. Assuming car is not home.");
587 		m_car.is_home = false;
588 	}
589 	else
590 	{
591 		std::string Latitude = strarray[0];
592 		std::string Longitude = strarray[1];
593 		double LaDz = std::stod(Latitude);
594 		double LoDz = std::stod(Longitude);
595 
596 		m_car.is_home = ((std::fabs(LaDz - data.latitude) < 2E-4) && (std::fabs(LoDz - data.longitude) < 2E-3) && !data.is_driving);
597 
598 		Log(LOG_NORM, "Location: %f %f Speed: %d Home: %s", data.latitude, data.longitude, data.speed, m_car.is_home ? "true" : "false");
599 
600 	}
601 }
602 
GetClimateState()603 bool CeVehicle::GetClimateState()
604 {
605 	CVehicleApi::tClimateData reply;
606 
607 	if (m_api->GetClimateData(reply))
608 	{
609 		UpdateClimateData(reply);
610 		return ConditionalReturn(true, Get_Climate_State);
611 	}
612 
613 	return ConditionalReturn(false, Get_Climate_State);
614 }
615 
UpdateClimateData(CVehicleApi::tClimateData & data)616 void CeVehicle::UpdateClimateData(CVehicleApi::tClimateData& data)
617 {
618 	m_car.climate_on = data.is_climate_on;
619 	m_car.defrost = data.is_defrost_on;
620 	SendTempSensor(VEHICLE_TEMP_INSIDE, 255, data.inside_temp, m_Name + " Temperature");
621 	SendTempSensor(VEHICLE_TEMP_OUTSIDE, 255, data.outside_temp, m_Name + " Outside Temperature");
622 	SendSwitch(VEHICLE_SWITCH_CLIMATE, 1, 255, m_car.climate_on, 0, m_Name + " Climate switch");
623 	SendSwitch(VEHICLE_SWITCH_DEFROST, 1, 255, m_car.defrost, 0, m_Name + " Defrost switch");
624 }
625 
GetChargeState()626 bool CeVehicle::GetChargeState()
627 {
628 	CVehicleApi::tChargeData reply;
629 
630 	if (m_api->GetChargeData(reply))
631 	{
632 		UpdateChargeData(reply);
633 		return ConditionalReturn(true, Get_Charge_State);
634 	}
635 
636 	return ConditionalReturn(false, Get_Charge_State);
637 }
638 
UpdateChargeData(CVehicleApi::tChargeData & data)639 void CeVehicle::UpdateChargeData(CVehicleApi::tChargeData& data)
640 {
641 	SendPercentageSensor(VEHICLE_LEVEL_BATTERY, 1, static_cast<int>(data.battery_level), data.battery_level, m_Name + " Battery Level");
642 	m_car.connected = data.is_connected;
643 	m_car.charging = data.is_charging;
644 	m_car.charge_state = data.status_string;
645 	SendSwitch(VEHICLE_SWITCH_CHARGE, 1, 255, m_car.charging, 0, m_Name + " Charge switch");
646 }
647