hyundai-kia-connect-api 3.17.6__py2.py3-none-any.whl → 3.32.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hyundai_kia_connect_api/ApiImpl.py +102 -29
- hyundai_kia_connect_api/ApiImplType1.py +321 -0
- hyundai_kia_connect_api/{HyundaiBlueLinkAPIUSA.py → HyundaiBlueLinkApiUSA.py} +295 -35
- hyundai_kia_connect_api/KiaUvoApiAU.py +77 -88
- hyundai_kia_connect_api/KiaUvoApiCA.py +70 -41
- hyundai_kia_connect_api/KiaUvoApiCN.py +30 -42
- hyundai_kia_connect_api/KiaUvoApiEU.py +466 -376
- hyundai_kia_connect_api/{KiaUvoAPIUSA.py → KiaUvoApiUSA.py} +98 -102
- hyundai_kia_connect_api/Vehicle.py +142 -19
- hyundai_kia_connect_api/VehicleManager.py +60 -19
- hyundai_kia_connect_api/__init__.py +5 -6
- hyundai_kia_connect_api/bluelink.py +457 -0
- hyundai_kia_connect_api/const.py +12 -1
- hyundai_kia_connect_api/utils.py +30 -3
- {hyundai_kia_connect_api-3.17.6.dist-info → hyundai_kia_connect_api-3.32.0.dist-info}/LICENSE +0 -1
- {hyundai_kia_connect_api-3.17.6.dist-info → hyundai_kia_connect_api-3.32.0.dist-info}/METADATA +53 -18
- hyundai_kia_connect_api-3.32.0.dist-info/RECORD +23 -0
- {hyundai_kia_connect_api-3.17.6.dist-info → hyundai_kia_connect_api-3.32.0.dist-info}/WHEEL +1 -1
- hyundai_kia_connect_api-3.32.0.dist-info/entry_points.txt +2 -0
- hyundai_kia_connect_api-3.17.6.dist-info/RECORD +0 -20
- {hyundai_kia_connect_api-3.17.6.dist-info → hyundai_kia_connect_api-3.32.0.dist-info}/AUTHORS.rst +0 -0
- {hyundai_kia_connect_api-3.17.6.dist-info → hyundai_kia_connect_api-3.32.0.dist-info}/top_level.txt +0 -0
@@ -4,13 +4,16 @@
|
|
4
4
|
|
5
5
|
import logging
|
6
6
|
import time
|
7
|
-
import re
|
8
7
|
import datetime as dt
|
9
8
|
import pytz
|
10
9
|
import requests
|
10
|
+
import certifi
|
11
|
+
|
11
12
|
from requests.adapters import HTTPAdapter
|
12
13
|
from urllib3.util.ssl_ import create_urllib3_context
|
13
14
|
|
15
|
+
from hyundai_kia_connect_api.exceptions import APIError
|
16
|
+
|
14
17
|
from .const import (
|
15
18
|
DOMAIN,
|
16
19
|
VEHICLE_LOCK_ACTION,
|
@@ -19,10 +22,17 @@ from .const import (
|
|
19
22
|
TEMPERATURE_UNITS,
|
20
23
|
ENGINE_TYPES,
|
21
24
|
)
|
22
|
-
from .utils import get_child_value, get_float
|
25
|
+
from .utils import get_child_value, get_float, parse_datetime
|
23
26
|
from .ApiImpl import ApiImpl, ClimateRequestOptions
|
24
27
|
from .Token import Token
|
25
|
-
from .Vehicle import
|
28
|
+
from .Vehicle import (
|
29
|
+
DailyDrivingStats,
|
30
|
+
DayTripCounts,
|
31
|
+
DayTripInfo,
|
32
|
+
MonthTripInfo,
|
33
|
+
TripInfo,
|
34
|
+
Vehicle,
|
35
|
+
)
|
26
36
|
|
27
37
|
|
28
38
|
CIPHERS = "DEFAULT@SECLEVEL=1"
|
@@ -43,7 +53,7 @@ class cipherAdapter(HTTPAdapter):
|
|
43
53
|
|
44
54
|
def init_poolmanager(self, *args, **kwargs):
|
45
55
|
kwargs["ssl_context"] = self._setup_ssl_context()
|
46
|
-
|
56
|
+
kwargs["ca_certs"] = certifi.where()
|
47
57
|
return super().init_poolmanager(*args, **kwargs)
|
48
58
|
|
49
59
|
def proxy_manager_for(self, *args, **kwargs):
|
@@ -52,8 +62,8 @@ class cipherAdapter(HTTPAdapter):
|
|
52
62
|
return super().proxy_manager_for(*args, **kwargs)
|
53
63
|
|
54
64
|
|
55
|
-
class
|
56
|
-
"""
|
65
|
+
class HyundaiBlueLinkApiUSA(ApiImpl):
|
66
|
+
"""HyundaiBlueLinkApiUSA"""
|
57
67
|
|
58
68
|
# initialize with a timestamp which will allow the first fetch to occur
|
59
69
|
last_loc_timestamp = dt.datetime.now(pytz.utc) - dt.timedelta(hours=3)
|
@@ -235,8 +245,8 @@ class HyundaiBlueLinkAPIUSA(ApiImpl):
|
|
235
245
|
return None
|
236
246
|
|
237
247
|
def _update_vehicle_properties(self, vehicle: Vehicle, state: dict) -> None:
|
238
|
-
vehicle.last_updated_at =
|
239
|
-
get_child_value(state, "vehicleStatus.dateTime")
|
248
|
+
vehicle.last_updated_at = parse_datetime(
|
249
|
+
get_child_value(state, "vehicleStatus.dateTime"), self.data_timezone
|
240
250
|
)
|
241
251
|
vehicle.total_driving_range = (
|
242
252
|
get_child_value(
|
@@ -376,6 +386,22 @@ class HyundaiBlueLinkAPIUSA(ApiImpl):
|
|
376
386
|
vehicle.ev_battery_is_plugged_in = get_child_value(
|
377
387
|
state, "vehicleStatus.evStatus.batteryPlugin"
|
378
388
|
)
|
389
|
+
vehicle.ev_charging_power = get_child_value(
|
390
|
+
state, "vehicleStatus.evStatus.batteryPower.batteryStndChrgPower"
|
391
|
+
)
|
392
|
+
ChargeDict = get_child_value(
|
393
|
+
state, "vehicleStatus.evStatus.reservChargeInfos.targetSOClist"
|
394
|
+
)
|
395
|
+
try:
|
396
|
+
vehicle.ev_charge_limits_ac = [
|
397
|
+
x["targetSOClevel"] for x in ChargeDict if x["plugType"] == 1
|
398
|
+
][-1]
|
399
|
+
vehicle.ev_charge_limits_dc = [
|
400
|
+
x["targetSOClevel"] for x in ChargeDict if x["plugType"] == 0
|
401
|
+
][-1]
|
402
|
+
except Exception:
|
403
|
+
_LOGGER.debug(f"{DOMAIN} - SOC Levels couldn't be found. May not be an EV.")
|
404
|
+
|
379
405
|
vehicle.ev_driving_range = (
|
380
406
|
get_child_value(
|
381
407
|
state,
|
@@ -428,14 +454,41 @@ class HyundaiBlueLinkAPIUSA(ApiImpl):
|
|
428
454
|
vehicle.location = (
|
429
455
|
get_child_value(state, "vehicleStatus.vehicleLocation.coord.lat"),
|
430
456
|
get_child_value(state, "vehicleStatus.vehicleLocation.coord.lon"),
|
431
|
-
|
432
|
-
get_child_value(state, "vehicleStatus.vehicleLocation.time")
|
457
|
+
parse_datetime(
|
458
|
+
get_child_value(state, "vehicleStatus.vehicleLocation.time"),
|
459
|
+
self.data_timezone,
|
433
460
|
),
|
434
461
|
)
|
435
462
|
vehicle.air_control_is_on = get_child_value(state, "vehicleStatus.airCtrlOn")
|
436
463
|
|
464
|
+
# fill vehicle.daily_stats
|
437
465
|
tripStats = []
|
438
466
|
tripDetails = get_child_value(state, "evTripDetails.tripdetails") or {}
|
467
|
+
|
468
|
+
# compute more digits for distance mileage using odometer and overrule distance
|
469
|
+
previous_odometer = None
|
470
|
+
for trip in reversed(tripDetails):
|
471
|
+
odometer = get_child_value(trip, "odometer.value")
|
472
|
+
if previous_odometer and odometer:
|
473
|
+
delta_odometer = odometer - previous_odometer
|
474
|
+
if delta_odometer >= 0.0:
|
475
|
+
trip["distance"] = delta_odometer
|
476
|
+
previous_odometer = odometer
|
477
|
+
|
478
|
+
# overrule odometer with more accuracy from last trip
|
479
|
+
if (
|
480
|
+
previous_odometer
|
481
|
+
and vehicle.odometer
|
482
|
+
and previous_odometer > vehicle.odometer
|
483
|
+
):
|
484
|
+
_LOGGER.debug(
|
485
|
+
f"Overruling odometer: {previous_odometer:.1f} old: {vehicle.odometer:.1f}" # noqa
|
486
|
+
)
|
487
|
+
vehicle.odometer = (
|
488
|
+
previous_odometer,
|
489
|
+
DISTANCE_UNITS[3],
|
490
|
+
)
|
491
|
+
|
439
492
|
for trip in tripDetails:
|
440
493
|
processedTrip = DailyDrivingStats(
|
441
494
|
date=dt.datetime.strptime(trip["startdate"], "%Y-%m-%d %H:%M:%S.%f"),
|
@@ -446,13 +499,185 @@ class HyundaiBlueLinkAPIUSA(ApiImpl):
|
|
446
499
|
battery_care_consumption=get_child_value(trip, "batterycare"),
|
447
500
|
regenerated_energy=get_child_value(trip, "regen"),
|
448
501
|
distance=get_child_value(trip, "distance"),
|
502
|
+
distance_unit=vehicle.odometer_unit,
|
449
503
|
)
|
450
504
|
tripStats.append(processedTrip)
|
451
505
|
|
452
506
|
vehicle.daily_stats = tripStats
|
453
507
|
|
508
|
+
# remember trips, store state
|
509
|
+
trips = []
|
510
|
+
for trip in tripDetails:
|
511
|
+
yyyymmdd_hhmmss = trip["startdate"] # remember full date
|
512
|
+
drive_time = int(get_child_value(trip["mileagetime"], "value"))
|
513
|
+
idle_time = int(get_child_value(trip["duration"], "value")) - drive_time
|
514
|
+
processed_trip = TripInfo(
|
515
|
+
hhmmss=yyyymmdd_hhmmss,
|
516
|
+
drive_time=int(drive_time / 60), # convert seconds to minutes
|
517
|
+
idle_time=int(idle_time / 60), # convert seconds to minutes
|
518
|
+
distance=float(trip["distance"]),
|
519
|
+
avg_speed=get_child_value(trip["avgspeed"], "value"),
|
520
|
+
max_speed=int(get_child_value(trip["maxspeed"], "value")),
|
521
|
+
)
|
522
|
+
trips.append(processed_trip)
|
523
|
+
|
524
|
+
_LOGGER.debug(f"_update_vehicle_properties filled_trips: {trips}")
|
525
|
+
if len(trips) > 0:
|
526
|
+
state["filled_trips"] = trips
|
527
|
+
|
454
528
|
vehicle.data = state
|
455
529
|
|
530
|
+
def update_month_trip_info(
|
531
|
+
self,
|
532
|
+
token,
|
533
|
+
vehicle,
|
534
|
+
yyyymm_string,
|
535
|
+
) -> None:
|
536
|
+
"""
|
537
|
+
feature only available for some regions.
|
538
|
+
Updates the vehicle.month_trip_info for the specified month.
|
539
|
+
|
540
|
+
Default this information is None:
|
541
|
+
|
542
|
+
month_trip_info: MonthTripInfo = None
|
543
|
+
"""
|
544
|
+
_LOGGER.debug(f"update_month_trip_info: {yyyymm_string}")
|
545
|
+
vehicle.month_trip_info = None
|
546
|
+
|
547
|
+
if vehicle.data is None or "filled_trips" not in vehicle.data:
|
548
|
+
_LOGGER.debug(f"filled_trips is empty: {vehicle.data}")
|
549
|
+
return # nothing to fill
|
550
|
+
|
551
|
+
trips = vehicle.data["filled_trips"]
|
552
|
+
|
553
|
+
month_trip_info: MonthTripInfo = None
|
554
|
+
month_trip_info_count = 0
|
555
|
+
|
556
|
+
for trip in trips:
|
557
|
+
date_str = trip.hhmmss
|
558
|
+
yyyymm = date_str[0:4] + date_str[5:7]
|
559
|
+
if yyyymm == yyyymm_string:
|
560
|
+
if month_trip_info_count == 0:
|
561
|
+
month_trip_info = MonthTripInfo(
|
562
|
+
yyyymm=yyyymm_string,
|
563
|
+
summary=TripInfo(
|
564
|
+
drive_time=trip.drive_time,
|
565
|
+
idle_time=trip.idle_time,
|
566
|
+
distance=trip.distance,
|
567
|
+
avg_speed=trip.avg_speed,
|
568
|
+
max_speed=trip.max_speed,
|
569
|
+
),
|
570
|
+
day_list=[],
|
571
|
+
)
|
572
|
+
month_trip_info_count = 1
|
573
|
+
else:
|
574
|
+
# increment totals for month (for the few trips available)
|
575
|
+
month_trip_info_count += 1
|
576
|
+
summary = month_trip_info.summary
|
577
|
+
summary.drive_time += trip.drive_time
|
578
|
+
summary.idle_time += trip.idle_time
|
579
|
+
summary.distance += trip.distance
|
580
|
+
summary.avg_speed += trip.avg_speed
|
581
|
+
summary.max_speed = max(trip.max_speed, summary.max_speed)
|
582
|
+
|
583
|
+
month_trip_info.summary.avg_speed /= month_trip_info_count
|
584
|
+
month_trip_info.summary.avg_speed = round(
|
585
|
+
month_trip_info.summary.avg_speed, 1
|
586
|
+
)
|
587
|
+
|
588
|
+
# also fill DayTripCount
|
589
|
+
yyyymmdd = yyyymm + date_str[8:10]
|
590
|
+
day_trip_found = False
|
591
|
+
for day in month_trip_info.day_list:
|
592
|
+
if day.yyyymmdd == yyyymmdd:
|
593
|
+
day.trip_count += 1
|
594
|
+
day_trip_found = True
|
595
|
+
|
596
|
+
if not day_trip_found:
|
597
|
+
month_trip_info.day_list.append(
|
598
|
+
DayTripCounts(yyyymmdd=yyyymmdd, trip_count=1)
|
599
|
+
)
|
600
|
+
|
601
|
+
vehicle.month_trip_info = month_trip_info
|
602
|
+
|
603
|
+
def update_day_trip_info(
|
604
|
+
self,
|
605
|
+
token,
|
606
|
+
vehicle,
|
607
|
+
yyyymmdd_string,
|
608
|
+
) -> None:
|
609
|
+
"""
|
610
|
+
feature only available for some regions.
|
611
|
+
Updates the vehicle.day_trip_info information for the specified day.
|
612
|
+
|
613
|
+
Default this information is None:
|
614
|
+
|
615
|
+
day_trip_info: DayTripInfo = None
|
616
|
+
"""
|
617
|
+
_LOGGER.debug(f"update_day_trip_info: {yyyymmdd_string}")
|
618
|
+
vehicle.day_trip_info = None
|
619
|
+
|
620
|
+
if vehicle.data is None or "filled_trips" not in vehicle.data:
|
621
|
+
_LOGGER.debug(f"filled_trips is empty: {vehicle.data}")
|
622
|
+
return # nothing to fill
|
623
|
+
|
624
|
+
trips = vehicle.data["filled_trips"]
|
625
|
+
_LOGGER.debug(f"filled_trips: {trips}")
|
626
|
+
|
627
|
+
day_trip_info: DayTripInfo = None
|
628
|
+
day_trip_info_count = 0
|
629
|
+
|
630
|
+
for trip in trips:
|
631
|
+
date_str = trip.hhmmss
|
632
|
+
yyyymmdd = date_str[0:4] + date_str[5:7] + date_str[8:10]
|
633
|
+
_LOGGER.debug(f"update_day_trip_info: {yyyymmdd} trip: {trip}")
|
634
|
+
if yyyymmdd == yyyymmdd_string:
|
635
|
+
if day_trip_info_count == 0:
|
636
|
+
day_trip_info = DayTripInfo(
|
637
|
+
yyyymmdd=yyyymmdd_string,
|
638
|
+
summary=TripInfo(
|
639
|
+
drive_time=trip.drive_time,
|
640
|
+
idle_time=trip.idle_time,
|
641
|
+
distance=trip.distance,
|
642
|
+
avg_speed=trip.avg_speed,
|
643
|
+
max_speed=trip.max_speed,
|
644
|
+
),
|
645
|
+
trip_list=[],
|
646
|
+
)
|
647
|
+
day_trip_info_count = 1
|
648
|
+
else:
|
649
|
+
# increment totals for month (for the few trips available)
|
650
|
+
day_trip_info_count += 1
|
651
|
+
summary = day_trip_info.summary
|
652
|
+
summary.drive_time += trip.drive_time
|
653
|
+
summary.idle_time += trip.idle_time
|
654
|
+
summary.distance += trip.distance
|
655
|
+
summary.avg_speed += trip.avg_speed
|
656
|
+
summary.max_speed = max(trip.max_speed, summary.max_speed)
|
657
|
+
|
658
|
+
day_trip_info.summary.avg_speed /= day_trip_info_count
|
659
|
+
day_trip_info.summary.avg_speed = round(
|
660
|
+
day_trip_info.summary.avg_speed, 1
|
661
|
+
)
|
662
|
+
|
663
|
+
# also fill TripInfo
|
664
|
+
hhmmss = date_str[11:13] + date_str[14:16] + date_str[17:19]
|
665
|
+
day_trip_info.trip_list.append(
|
666
|
+
TripInfo(
|
667
|
+
hhmmss=hhmmss,
|
668
|
+
drive_time=trip.drive_time,
|
669
|
+
idle_time=trip.idle_time,
|
670
|
+
distance=trip.distance,
|
671
|
+
avg_speed=trip.avg_speed,
|
672
|
+
max_speed=trip.max_speed,
|
673
|
+
)
|
674
|
+
)
|
675
|
+
_LOGGER.debug(
|
676
|
+
f"update_day_trip_info: trip_list result: {day_trip_info.trip_list}"
|
677
|
+
)
|
678
|
+
|
679
|
+
vehicle.day_trip_info = day_trip_info
|
680
|
+
|
456
681
|
def update_vehicle_with_cached_state(self, token: Token, vehicle: Vehicle) -> None:
|
457
682
|
state = {}
|
458
683
|
state["vehicleDetails"] = self._get_vehicle_details(token, vehicle)
|
@@ -538,6 +763,8 @@ class HyundaiBlueLinkAPIUSA(ApiImpl):
|
|
538
763
|
elif action == VEHICLE_LOCK_ACTION.UNLOCK:
|
539
764
|
url = self.API_URL + "rcs/rdo/on"
|
540
765
|
_LOGGER.debug(f"{DOMAIN} - Calling unlock")
|
766
|
+
else:
|
767
|
+
raise APIError(f"Invalid action value: {action}")
|
541
768
|
|
542
769
|
headers = self._get_vehicle_headers(token, vehicle)
|
543
770
|
headers["APPCLOUD-VIN"] = vehicle.VIN
|
@@ -636,32 +863,65 @@ class HyundaiBlueLinkAPIUSA(ApiImpl):
|
|
636
863
|
_LOGGER.debug(f"{DOMAIN} - Stop engine response: {response.text}")
|
637
864
|
|
638
865
|
def start_charge(self, token: Token, vehicle: Vehicle) -> None:
|
639
|
-
|
866
|
+
if vehicle.engine_type != ENGINE_TYPES.EV:
|
867
|
+
return {}
|
868
|
+
|
869
|
+
_LOGGER.debug(f"{DOMAIN} - Start charging..")
|
870
|
+
|
871
|
+
url = self.API_URL + "evc/charge/start"
|
872
|
+
headers = self._get_vehicle_headers(token, vehicle)
|
873
|
+
_LOGGER.debug(f"{DOMAIN} - Start charging headers: {headers}")
|
874
|
+
|
875
|
+
response = self.sessions.post(url, headers=headers)
|
876
|
+
_LOGGER.debug(
|
877
|
+
f"{DOMAIN} - Start charge response status code: {response.status_code}"
|
878
|
+
)
|
879
|
+
_LOGGER.debug(f"{DOMAIN} - Start charge response: {response.text}")
|
640
880
|
|
641
881
|
def stop_charge(self, token: Token, vehicle: Vehicle) -> None:
|
642
|
-
|
882
|
+
if vehicle.engine_type != ENGINE_TYPES.EV:
|
883
|
+
return {}
|
643
884
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
m = re.match(r"(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})", value)
|
656
|
-
value = dt.datetime(
|
657
|
-
year=int(m.group(1)),
|
658
|
-
month=int(m.group(2)),
|
659
|
-
day=int(m.group(3)),
|
660
|
-
hour=int(m.group(4)),
|
661
|
-
minute=int(m.group(5)),
|
662
|
-
second=int(m.group(6)),
|
663
|
-
tzinfo=self.data_timezone,
|
664
|
-
)
|
885
|
+
_LOGGER.debug(f"{DOMAIN} - Stop charging..")
|
886
|
+
|
887
|
+
url = self.API_URL + "evc/charge/stop"
|
888
|
+
headers = self._get_vehicle_headers(token, vehicle)
|
889
|
+
_LOGGER.debug(f"{DOMAIN} - Stop charging headers: {headers}")
|
890
|
+
|
891
|
+
response = self.sessions.post(url, headers=headers)
|
892
|
+
_LOGGER.debug(
|
893
|
+
f"{DOMAIN} - Stop charge response status code: {response.status_code}"
|
894
|
+
)
|
895
|
+
_LOGGER.debug(f"{DOMAIN} - Stop charge response: {response.text}")
|
665
896
|
|
666
|
-
|
667
|
-
|
897
|
+
def set_charge_limits(
|
898
|
+
self, token: Token, vehicle: Vehicle, ac: int, dc: int
|
899
|
+
) -> str:
|
900
|
+
if vehicle.engine_type != ENGINE_TYPES.EV:
|
901
|
+
return {}
|
902
|
+
|
903
|
+
_LOGGER.debug(f"{DOMAIN} - Setting charge limits..")
|
904
|
+
url = self.API_URL + "evc/charge/targetsoc/set"
|
905
|
+
headers = self._get_vehicle_headers(token, vehicle)
|
906
|
+
_LOGGER.debug(f"{DOMAIN} - Setting charge limits: {headers}")
|
907
|
+
|
908
|
+
data = {
|
909
|
+
"targetSOClist": [
|
910
|
+
{
|
911
|
+
"plugType": 0,
|
912
|
+
"targetSOClevel": int(dc),
|
913
|
+
},
|
914
|
+
{
|
915
|
+
"plugType": 1,
|
916
|
+
"targetSOClevel": int(ac),
|
917
|
+
},
|
918
|
+
]
|
919
|
+
}
|
920
|
+
|
921
|
+
_LOGGER.debug(f"{DOMAIN} - Setting charge limits body: {data}")
|
922
|
+
|
923
|
+
response = self.sessions.post(url, json=data, headers=headers)
|
924
|
+
_LOGGER.debug(
|
925
|
+
f"{DOMAIN} - Setting charge limits response status code: {response.status_code}" # noqa
|
926
|
+
)
|
927
|
+
_LOGGER.debug(f"{DOMAIN} - Setting charge limits: {response.text}")
|