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