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,7 +4,6 @@
|
|
4
4
|
import datetime as dt
|
5
5
|
import logging
|
6
6
|
import random
|
7
|
-
import re
|
8
7
|
import secrets
|
9
8
|
import ssl
|
10
9
|
import string
|
@@ -12,6 +11,7 @@ import time
|
|
12
11
|
import typing
|
13
12
|
from datetime import datetime
|
14
13
|
|
14
|
+
import certifi
|
15
15
|
import pytz
|
16
16
|
import requests
|
17
17
|
from requests import RequestException, Response
|
@@ -29,7 +29,7 @@ from .const import (
|
|
29
29
|
TEMPERATURE_UNITS,
|
30
30
|
VEHICLE_LOCK_ACTION,
|
31
31
|
)
|
32
|
-
from .utils import get_child_value
|
32
|
+
from .utils import get_child_value, parse_datetime
|
33
33
|
|
34
34
|
_LOGGER = logging.getLogger(__name__)
|
35
35
|
|
@@ -41,7 +41,9 @@ class KiaSSLAdapter(HTTPAdapter):
|
|
41
41
|
context = create_urllib3_context(
|
42
42
|
ciphers="DEFAULT:@SECLEVEL=1", ssl_version=ssl.PROTOCOL_TLSv1_2
|
43
43
|
)
|
44
|
+
context.options |= 0x4
|
44
45
|
kwargs["ssl_context"] = context
|
46
|
+
kwargs["ca_certs"] = certifi.where()
|
45
47
|
return super().init_poolmanager(*args, **kwargs)
|
46
48
|
|
47
49
|
|
@@ -104,8 +106,8 @@ def request_with_logging(func):
|
|
104
106
|
return request_with_logging_wrapper
|
105
107
|
|
106
108
|
|
107
|
-
class
|
108
|
-
"""
|
109
|
+
class KiaUvoApiUSA(ApiImpl):
|
110
|
+
"""KiaUvoApiUSA"""
|
109
111
|
|
110
112
|
def __init__(self, region: int, brand: int, language) -> None:
|
111
113
|
self.LANGUAGE: str = language
|
@@ -122,8 +124,14 @@ class KiaUvoAPIUSA(ApiImpl):
|
|
122
124
|
|
123
125
|
self.BASE_URL: str = "api.owners.kia.com"
|
124
126
|
self.API_URL: str = "https://" + self.BASE_URL + "/apigw/v1/"
|
125
|
-
self.
|
126
|
-
|
127
|
+
self._session = None
|
128
|
+
|
129
|
+
@property
|
130
|
+
def session(self):
|
131
|
+
if not self._session:
|
132
|
+
self._session = requests.Session()
|
133
|
+
self._session.mount("https://", KiaSSLAdapter())
|
134
|
+
return self._session
|
127
135
|
|
128
136
|
def api_headers(self) -> dict:
|
129
137
|
offset = time.localtime().tm_gmtoff / 60 / 60
|
@@ -278,10 +286,11 @@ class KiaUvoAPIUSA(ApiImpl):
|
|
278
286
|
|
279
287
|
def _update_vehicle_properties(self, vehicle: Vehicle, state: dict) -> None:
|
280
288
|
"""Get cached vehicle data and update Vehicle instance with it"""
|
281
|
-
vehicle.last_updated_at =
|
289
|
+
vehicle.last_updated_at = parse_datetime(
|
282
290
|
get_child_value(
|
283
291
|
state, "lastVehicleInfo.vehicleStatusRpt.vehicleStatus.syncDate.utc"
|
284
|
-
)
|
292
|
+
),
|
293
|
+
self.data_timezone,
|
285
294
|
)
|
286
295
|
vehicle.odometer = (
|
287
296
|
get_child_value(state, "vehicleConfig.vehicleDetail.vehicle.mileage"),
|
@@ -417,7 +426,7 @@ class KiaUvoAPIUSA(ApiImpl):
|
|
417
426
|
vehicle.ev_charge_limits_dc = [
|
418
427
|
x["targetSOClevel"] for x in ChargeDict if x["plugType"] == 0
|
419
428
|
][-1]
|
420
|
-
except:
|
429
|
+
except Exception:
|
421
430
|
_LOGGER.debug(f"{DOMAIN} - SOC Levels couldn't be found. May not be an EV.")
|
422
431
|
|
423
432
|
vehicle.ev_driving_range = (
|
@@ -515,8 +524,9 @@ class KiaUvoAPIUSA(ApiImpl):
|
|
515
524
|
vehicle.location = (
|
516
525
|
get_child_value(state, "lastVehicleInfo.location.coord.lat"),
|
517
526
|
get_child_value(state, "lastVehicleInfo.location.coord.lon"),
|
518
|
-
|
519
|
-
get_child_value(state, "lastVehicleInfo.location.syncDate.utc")
|
527
|
+
parse_datetime(
|
528
|
+
get_child_value(state, "lastVehicleInfo.location.syncDate.utc"),
|
529
|
+
self.data_timezone,
|
520
530
|
),
|
521
531
|
)
|
522
532
|
|
@@ -534,25 +544,6 @@ class KiaUvoAPIUSA(ApiImpl):
|
|
534
544
|
|
535
545
|
vehicle.data = state
|
536
546
|
|
537
|
-
def get_last_updated_at(self, value) -> dt.datetime:
|
538
|
-
_LOGGER.debug(f"{DOMAIN} - last_updated_at - before {value}")
|
539
|
-
if value is None:
|
540
|
-
value = dt.datetime(2000, 1, 1, tzinfo=self.data_timezone)
|
541
|
-
else:
|
542
|
-
m = re.match(r"(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})", value)
|
543
|
-
value = dt.datetime(
|
544
|
-
year=int(m.group(1)),
|
545
|
-
month=int(m.group(2)),
|
546
|
-
day=int(m.group(3)),
|
547
|
-
hour=int(m.group(4)),
|
548
|
-
minute=int(m.group(5)),
|
549
|
-
second=int(m.group(6)),
|
550
|
-
tzinfo=self.data_timezone,
|
551
|
-
)
|
552
|
-
|
553
|
-
_LOGGER.debug(f"{DOMAIN} - last_updated_at - after {value}")
|
554
|
-
return value
|
555
|
-
|
556
547
|
def _get_cached_vehicle_state(self, token: Token, vehicle: Vehicle) -> dict:
|
557
548
|
url = self.API_URL + "cmm/gvi"
|
558
549
|
|
@@ -629,6 +620,59 @@ class KiaUvoAPIUSA(ApiImpl):
|
|
629
620
|
|
630
621
|
return response.headers["Xid"]
|
631
622
|
|
623
|
+
def _seat_settings(self, level) -> dict:
|
624
|
+
# See const.SEAT_STATUS for the list and descriptions of levels.
|
625
|
+
#
|
626
|
+
# The values were determined empirically, see https://github.com/Hyundai-Kia-Connect/kia_uvo/issues/718
|
627
|
+
if level == 8: # High heat
|
628
|
+
return {
|
629
|
+
"heatVentType": 1,
|
630
|
+
"heatVentLevel": 4,
|
631
|
+
"heatVentStep": 1,
|
632
|
+
}
|
633
|
+
elif level == 7: # Medium heat
|
634
|
+
return {
|
635
|
+
"heatVentType": 1,
|
636
|
+
"heatVentLevel": 3,
|
637
|
+
"heatVentStep": 2,
|
638
|
+
}
|
639
|
+
elif level == 6: # Low heat
|
640
|
+
return {
|
641
|
+
"heatVentType": 1,
|
642
|
+
"heatVentLevel": 2,
|
643
|
+
"heatVentStep": 3,
|
644
|
+
}
|
645
|
+
elif level == 5: # High cool
|
646
|
+
return {
|
647
|
+
"heatVentType": 2,
|
648
|
+
"heatVentLevel": 4,
|
649
|
+
"heatVentStep": 1,
|
650
|
+
}
|
651
|
+
elif level == 4: # Medium cool
|
652
|
+
return {
|
653
|
+
"heatVentType": 2,
|
654
|
+
"heatVentLevel": 3,
|
655
|
+
"heatVentStep": 2,
|
656
|
+
}
|
657
|
+
elif level == 3: # Low cool
|
658
|
+
return {
|
659
|
+
"heatVentType": 2,
|
660
|
+
"heatVentLevel": 2,
|
661
|
+
"heatVentStep": 3,
|
662
|
+
}
|
663
|
+
elif level == 1: # Generically on, let's assume high heat
|
664
|
+
return {
|
665
|
+
"heatVentType": 1,
|
666
|
+
"heatVentLevel": 4,
|
667
|
+
"heatVentStep": 1,
|
668
|
+
}
|
669
|
+
else: # Off
|
670
|
+
return {
|
671
|
+
"heatVentType": 0,
|
672
|
+
"heatVentLevel": 1,
|
673
|
+
"heatVentStep": 0,
|
674
|
+
}
|
675
|
+
|
632
676
|
def start_climate(
|
633
677
|
self, token: Token, vehicle: Vehicle, options: ClimateRequestOptions
|
634
678
|
) -> str:
|
@@ -647,93 +691,45 @@ class KiaUvoAPIUSA(ApiImpl):
|
|
647
691
|
options.defrost = False
|
648
692
|
if options.duration is None:
|
649
693
|
options.duration = 5
|
650
|
-
if options.
|
651
|
-
options.
|
652
|
-
if options.front_right_seat is None:
|
653
|
-
options.front_right_seat = 0
|
654
|
-
if options.rear_left_seat is None:
|
655
|
-
options.rear_left_seat = 0
|
656
|
-
if options.rear_right_seat is None:
|
657
|
-
options.rear_right_seat = 0
|
658
|
-
|
659
|
-
front_left_heatVentType = 0
|
660
|
-
front_right_heatVentType = 0
|
661
|
-
rear_left_heatVentType = 0
|
662
|
-
rear_right_heatVentType = 0
|
663
|
-
front_left_heatVentLevel = 0
|
664
|
-
front_right_heatVentLevel = 0
|
665
|
-
rear_left_heatVentLevel = 0
|
666
|
-
rear_right_heatVentLevel = 0
|
667
|
-
|
668
|
-
# heated
|
669
|
-
if options.front_left_seat in (6, 7, 8):
|
670
|
-
front_left_heatVentType = 1
|
671
|
-
front_left_heatVentLevel = options.front_left_seat - 4
|
672
|
-
if options.front_right_seat in (6, 7, 8):
|
673
|
-
front_right_heatVentType = 1
|
674
|
-
front_right_heatVentLevel = options.front_right_seat - 4
|
675
|
-
if options.rear_left_seat in (6, 7, 8):
|
676
|
-
rear_left_heatVentType = 1
|
677
|
-
rear_left_heatVentLevel = options.rear_left_seat - 4
|
678
|
-
if options.rear_right_seat in (6, 7, 8):
|
679
|
-
rear_right_heatVentType = 1
|
680
|
-
rear_right_heatVentLevel = options.rear_right_seat - 4
|
681
|
-
|
682
|
-
# ventilated
|
683
|
-
if options.front_left_seat in (3, 4, 5):
|
684
|
-
front_left_heatVentType = 2
|
685
|
-
front_left_heatVentLevel = options.front_left_seat - 1
|
686
|
-
if options.front_right_seat in (3, 4, 5):
|
687
|
-
front_right_heatVentType = 2
|
688
|
-
front_right_heatVentLevel = options.front_right_seat - 1
|
689
|
-
if options.rear_left_seat in (3, 4, 5):
|
690
|
-
rear_left_heatVentType = 2
|
691
|
-
rear_left_heatVentLevel = options.rear_left_seat - 1
|
692
|
-
if options.rear_right_seat in (3, 4, 5):
|
693
|
-
rear_right_heatVentType = 2
|
694
|
-
rear_right_heatVentLevel = options.rear_right_seat - 1
|
694
|
+
if options.steering_wheel is None:
|
695
|
+
options.steering_wheel = 0
|
695
696
|
|
696
697
|
body = {
|
697
698
|
"remoteClimate": {
|
698
|
-
"airCtrl": options.climate,
|
699
699
|
"airTemp": {
|
700
700
|
"unit": 1,
|
701
701
|
"value": str(options.set_temp),
|
702
702
|
},
|
703
|
+
"airCtrl": options.climate,
|
703
704
|
"defrost": options.defrost,
|
704
705
|
"heatingAccessory": {
|
705
|
-
"rearWindow":
|
706
|
-
"sideMirror":
|
707
|
-
"steeringWheel":
|
706
|
+
"rearWindow": 1 if options.heating in [1, 2, 4] else 0,
|
707
|
+
"sideMirror": 1 if options.heating in [1, 4] else 0,
|
708
|
+
"steeringWheel": 1 if options.steering_wheel in [1, 2] else 0,
|
709
|
+
"steeringWheelStep": options.steering_wheel,
|
708
710
|
},
|
709
711
|
"ignitionOnDuration": {
|
710
712
|
"unit": 4,
|
711
713
|
"value": options.duration,
|
712
714
|
},
|
713
|
-
|
714
|
-
"driverSeat": {
|
715
|
-
"heatVentType": front_left_heatVentType,
|
716
|
-
"heatVentLevel": front_left_heatVentLevel,
|
717
|
-
"heatVentStep": 1,
|
718
|
-
},
|
719
|
-
"passengerSeat": {
|
720
|
-
"heatVentType": front_right_heatVentType,
|
721
|
-
"heatVentLevel": front_right_heatVentLevel,
|
722
|
-
"heatVentStep": 1,
|
723
|
-
},
|
724
|
-
"rearLeftSeat": {
|
725
|
-
"heatVentType": rear_left_heatVentType,
|
726
|
-
"heatVentLevel": rear_left_heatVentLevel,
|
727
|
-
"heatVentStep": 1,
|
728
|
-
},
|
729
|
-
"rearRightSeat": {
|
730
|
-
"heatVentType": rear_right_heatVentType,
|
731
|
-
"heatVentLevel": rear_right_heatVentLevel,
|
732
|
-
"heatVentStep": 1,
|
733
|
-
},
|
734
|
-
},
|
735
|
-
}
|
715
|
+
},
|
736
716
|
}
|
717
|
+
|
718
|
+
# Kia seems to now be checking if you can set the heated/vented seats at
|
719
|
+
# the car level only add to body if the option is not none for any of
|
720
|
+
# the seats
|
721
|
+
if (
|
722
|
+
options.front_left_seat is not None
|
723
|
+
or options.front_right_seat is not None
|
724
|
+
or options.rear_left_seat is not None
|
725
|
+
or options.rear_right_seat is not None
|
726
|
+
):
|
727
|
+
body["remoteClimate"]["heatVentSeat"] = {
|
728
|
+
"driverSeat": self._seat_settings(options.front_left_seat),
|
729
|
+
"passengerSeat": self._seat_settings(options.front_right_seat),
|
730
|
+
"rearLeftSeat": self._seat_settings(options.rear_left_seat),
|
731
|
+
"rearRightSeat": self._seat_settings(options.rear_right_seat),
|
732
|
+
}
|
737
733
|
_LOGGER.debug(f"{DOMAIN} - Planned start_climate payload: {body}")
|
738
734
|
response = self.post_request_with_logging_and_active_session(
|
739
735
|
token=token, url=url, json_body=body, vehicle=vehicle
|
@@ -1,12 +1,13 @@
|
|
1
|
-
# pylint:disable=missing-class-docstring,missing-function-docstring,wildcard-import,unused-wildcard-import,invalid-name
|
1
|
+
# pylint:disable=missing-class-docstring,missing-function-docstring,wildcard-import,unused-wildcard-import,invalid-name,logging-fstring-interpolation
|
2
2
|
"""Vehicle class"""
|
3
|
+
|
3
4
|
import logging
|
4
5
|
import datetime
|
5
6
|
import typing
|
6
7
|
from dataclasses import dataclass, field
|
7
8
|
|
8
|
-
from .utils import get_float
|
9
|
-
from .const import
|
9
|
+
from .utils import get_float, get_safe_local_datetime
|
10
|
+
from .const import DISTANCE_UNITS
|
10
11
|
|
11
12
|
_LOGGER = logging.getLogger(__name__)
|
12
13
|
|
@@ -16,9 +17,9 @@ class TripInfo:
|
|
16
17
|
"""Trip Info"""
|
17
18
|
|
18
19
|
hhmmss: str = None # will not be filled by summary
|
19
|
-
drive_time: int = None
|
20
|
-
idle_time: int = None
|
21
|
-
distance:
|
20
|
+
drive_time: int = None # minutes
|
21
|
+
idle_time: int = None # minutes
|
22
|
+
distance: float = None
|
22
23
|
avg_speed: float = None
|
23
24
|
max_speed: int = None
|
24
25
|
|
@@ -59,10 +60,8 @@ class DailyDrivingStats:
|
|
59
60
|
onboard_electronics_consumption: int = None
|
60
61
|
battery_care_consumption: int = None
|
61
62
|
regenerated_energy: int = None
|
62
|
-
|
63
|
-
|
64
|
-
distance: int = None
|
65
|
-
distance_unit = DISTANCE_UNITS[1] # set to kms by default
|
63
|
+
distance: float = None
|
64
|
+
distance_unit: str = DISTANCE_UNITS[1] # set to kms by default
|
66
65
|
|
67
66
|
|
68
67
|
@dataclass
|
@@ -93,8 +92,10 @@ class Vehicle:
|
|
93
92
|
|
94
93
|
car_battery_percentage: int = None
|
95
94
|
engine_is_running: bool = None
|
96
|
-
|
95
|
+
|
96
|
+
_last_updated_at: datetime.datetime = None
|
97
97
|
timezone: datetime.timezone = datetime.timezone.utc # default UTC
|
98
|
+
|
98
99
|
dtc_count: typing.Union[int, None] = None
|
99
100
|
dtc_descriptions: typing.Union[dict, None] = None
|
100
101
|
|
@@ -155,9 +156,13 @@ class Vehicle:
|
|
155
156
|
# EV fields (EV/PHEV)
|
156
157
|
|
157
158
|
ev_charge_port_door_is_open: typing.Union[bool, None] = None
|
159
|
+
ev_charging_power: typing.Union[float, None] = None # Charging power in kW
|
158
160
|
|
159
161
|
ev_charge_limits_dc: typing.Union[int, None] = None
|
160
162
|
ev_charge_limits_ac: typing.Union[int, None] = None
|
163
|
+
ev_charging_current: typing.Union[int, None] = (
|
164
|
+
None # Europe feature only, ac charging current limit
|
165
|
+
)
|
161
166
|
ev_v2l_discharge_limit: typing.Union[int, None] = None
|
162
167
|
|
163
168
|
# energy consumed and regenerated since the vehicle was paired with the account
|
@@ -169,11 +174,61 @@ class Vehicle:
|
|
169
174
|
# expressed in watt-hours (Wh)
|
170
175
|
power_consumption_30d: float = None # Europe feature only
|
171
176
|
|
172
|
-
#
|
173
|
-
|
177
|
+
# feature only available for some regions (getter/setter for sorting)
|
178
|
+
_daily_stats: list[DailyDrivingStats] = field(default_factory=list)
|
174
179
|
|
175
|
-
|
176
|
-
|
180
|
+
@property
|
181
|
+
def daily_stats(self):
|
182
|
+
return self._daily_stats
|
183
|
+
|
184
|
+
@daily_stats.setter
|
185
|
+
def daily_stats(self, value):
|
186
|
+
result = value
|
187
|
+
if result is not None and len(result) > 0: # sort on decreasing date
|
188
|
+
_LOGGER.debug(f"before daily_stats: {result}")
|
189
|
+
result.sort(reverse=True, key=lambda k: k.date)
|
190
|
+
_LOGGER.debug(f"after daily_stats: {result}")
|
191
|
+
self._daily_stats = result
|
192
|
+
|
193
|
+
# feature only available for some regions (getter/setter for sorting)
|
194
|
+
_month_trip_info: MonthTripInfo = None
|
195
|
+
|
196
|
+
@property
|
197
|
+
def month_trip_info(self):
|
198
|
+
return self._month_trip_info
|
199
|
+
|
200
|
+
@month_trip_info.setter
|
201
|
+
def month_trip_info(self, value):
|
202
|
+
result = value
|
203
|
+
if (
|
204
|
+
result is not None
|
205
|
+
and hasattr(result, "day_list")
|
206
|
+
and len(result.day_list) > 0
|
207
|
+
): # sort on increasing yyyymmdd
|
208
|
+
_LOGGER.debug(f"before month_trip_info: {result}")
|
209
|
+
result.day_list.sort(key=lambda k: k.yyyymmdd)
|
210
|
+
_LOGGER.debug(f"after month_trip_info: {result}")
|
211
|
+
self._month_trip_info = result
|
212
|
+
|
213
|
+
# feature only available for some regions (getter/setter for sorting)
|
214
|
+
_day_trip_info: DayTripInfo = None
|
215
|
+
|
216
|
+
@property
|
217
|
+
def day_trip_info(self):
|
218
|
+
return self._day_trip_info
|
219
|
+
|
220
|
+
@day_trip_info.setter
|
221
|
+
def day_trip_info(self, value):
|
222
|
+
result = value
|
223
|
+
if (
|
224
|
+
result is not None
|
225
|
+
and hasattr(result, "trip_list")
|
226
|
+
and len(result.trip_list) > 0
|
227
|
+
): # sort on descending hhmmss
|
228
|
+
_LOGGER.debug(f"before day_trip_info: {result}")
|
229
|
+
result.trip_list.sort(reverse=True, key=lambda k: k.hhmmss)
|
230
|
+
_LOGGER.debug(f"after day_trip_info: {result}")
|
231
|
+
self._day_trip_info = result
|
177
232
|
|
178
233
|
ev_battery_percentage: int = None
|
179
234
|
ev_battery_soh_percentage: int = None
|
@@ -219,10 +274,26 @@ class Vehicle:
|
|
219
274
|
ev_first_departure_time: typing.Union[datetime.time, None] = None
|
220
275
|
ev_second_departure_time: typing.Union[datetime.time, None] = None
|
221
276
|
|
277
|
+
ev_first_departure_climate_enabled: typing.Union[bool, None] = None
|
278
|
+
ev_second_departure_climate_enabled: typing.Union[bool, None] = None
|
279
|
+
|
280
|
+
_ev_first_departure_climate_temperature: typing.Union[float, None] = None
|
281
|
+
_ev_first_departure_climate_temperature_value: typing.Union[float, None] = None
|
282
|
+
_ev_first_departure_climate_temperature_unit: typing.Union[str, None] = None
|
283
|
+
|
284
|
+
_ev_second_departure_climate_temperature: typing.Union[float, None] = None
|
285
|
+
_ev_second_departure_climate_temperature_value: typing.Union[float, None] = None
|
286
|
+
_ev_second_departure_climate_temperature_unit: typing.Union[str, None] = None
|
287
|
+
|
288
|
+
ev_first_departure_climate_defrost: typing.Union[bool, None] = None
|
289
|
+
ev_second_departure_climate_defrost: typing.Union[bool, None] = None
|
290
|
+
|
222
291
|
ev_off_peak_start_time: typing.Union[datetime.time, None] = None
|
223
292
|
ev_off_peak_end_time: typing.Union[datetime.time, None] = None
|
224
293
|
ev_off_peak_charge_only_enabled: typing.Union[bool, None] = None
|
225
294
|
|
295
|
+
ev_schedule_charge_enabled: typing.Union[bool, None] = None
|
296
|
+
|
226
297
|
# IC fields (PHEV/HEV/IC)
|
227
298
|
_fuel_driving_range: float = None
|
228
299
|
_fuel_driving_range_value: float = None
|
@@ -243,8 +314,12 @@ class Vehicle:
|
|
243
314
|
|
244
315
|
@geocode.setter
|
245
316
|
def geocode(self, value):
|
246
|
-
|
247
|
-
|
317
|
+
if value:
|
318
|
+
self._geocode_name = value[0]
|
319
|
+
self._geocode_address = value[1]
|
320
|
+
else:
|
321
|
+
self._geocode_name = None
|
322
|
+
self._geocode_address = None
|
248
323
|
|
249
324
|
@property
|
250
325
|
def total_driving_range(self):
|
@@ -280,6 +355,26 @@ class Vehicle:
|
|
280
355
|
self._last_service_distance_unit = value[1]
|
281
356
|
self._last_service_distance = value[0]
|
282
357
|
|
358
|
+
@property
|
359
|
+
def last_updated_at(self):
|
360
|
+
return self._last_updated_at
|
361
|
+
|
362
|
+
@last_updated_at.setter
|
363
|
+
def last_updated_at(self, value):
|
364
|
+
# workaround for: Timestamp of "last_updated_at" sensor is wrong #931
|
365
|
+
# https://github.com/Hyundai-Kia-Connect/kia_uvo/issues/931#issuecomment-2381569934
|
366
|
+
newest_updated_at = get_safe_local_datetime(value)
|
367
|
+
previous_updated_at = self._last_updated_at
|
368
|
+
if newest_updated_at and previous_updated_at: # both filled
|
369
|
+
if newest_updated_at < previous_updated_at:
|
370
|
+
utcoffset = newest_updated_at.utcoffset()
|
371
|
+
newest_updated_at_corrected = newest_updated_at + utcoffset
|
372
|
+
if newest_updated_at_corrected >= previous_updated_at:
|
373
|
+
newest_updated_at = newest_updated_at_corrected
|
374
|
+
if newest_updated_at < previous_updated_at:
|
375
|
+
newest_updated_at = previous_updated_at # keep old because newer
|
376
|
+
self._last_updated_at = newest_updated_at
|
377
|
+
|
283
378
|
@property
|
284
379
|
def location_latitude(self):
|
285
380
|
return self._location_latitude
|
@@ -305,7 +400,7 @@ class Vehicle:
|
|
305
400
|
def location(self, value):
|
306
401
|
self._location_latitude = value[0]
|
307
402
|
self._location_longitude = value[1]
|
308
|
-
self._location_last_set_time = value[2]
|
403
|
+
self._location_last_set_time = get_safe_local_datetime(value[2])
|
309
404
|
|
310
405
|
@property
|
311
406
|
def odometer(self):
|
@@ -330,7 +425,7 @@ class Vehicle:
|
|
330
425
|
def air_temperature(self, value):
|
331
426
|
self._air_temperature_value = value[0]
|
332
427
|
self._air_temperature_unit = value[1]
|
333
|
-
self._air_temperature = value[0]
|
428
|
+
self._air_temperature = value[0] if value[0] != "OFF" else None
|
334
429
|
|
335
430
|
@property
|
336
431
|
def ev_driving_range(self):
|
@@ -414,6 +509,34 @@ class Vehicle:
|
|
414
509
|
self._ev_target_range_charge_DC_unit = value[1]
|
415
510
|
self._ev_target_range_charge_DC = value[0]
|
416
511
|
|
512
|
+
@property
|
513
|
+
def ev_first_departure_climate_temperature(self):
|
514
|
+
return self._ev_first_departure_climate_temperature
|
515
|
+
|
516
|
+
@property
|
517
|
+
def ev_first_departure_climate_temperature_unit(self):
|
518
|
+
return self._ev_first_departure_climate_temperature_unit
|
519
|
+
|
520
|
+
@ev_first_departure_climate_temperature.setter
|
521
|
+
def ev_first_departure_climate_temperature(self, value):
|
522
|
+
self._ev_first_departure_climate_temperature_value = value[0]
|
523
|
+
self._ev_first_departure_climate_temperature_unit = value[1]
|
524
|
+
self._ev_first_departure_climate_temperature = value[0]
|
525
|
+
|
526
|
+
@property
|
527
|
+
def ev_second_departure_climate_temperature(self):
|
528
|
+
return self._ev_second_departure_climate_temperature
|
529
|
+
|
530
|
+
@property
|
531
|
+
def ev_second_departure_climate_temperature_unit(self):
|
532
|
+
return self._ev_second_departure_climate_temperature_unit
|
533
|
+
|
534
|
+
@ev_second_departure_climate_temperature.setter
|
535
|
+
def ev_second_departure_climate_temperature(self, value):
|
536
|
+
self._ev_second_departure_climate_temperature_value = value[0]
|
537
|
+
self._ev_second_departure_climate_temperature_unit = value[1]
|
538
|
+
self._ev_second_departure_climate_temperature = value[0]
|
539
|
+
|
417
540
|
@property
|
418
541
|
def fuel_driving_range(self):
|
419
542
|
return self._fuel_driving_range
|