Hyundai-Kia-Connect / hyundai_kia_connect_api

This is a Kia UVO and Hyundai Bluelink written in python. It is primary consumed by home assistant. If you are looking for a home assistant Kia / Hyundai implementation please look here: https://github.com/Hyundai-Kia-Connect/kia_uvo. Much of this base code came from reading bluelinky and contributions to the kia_uvo home assistant project.
MIT License
129 stars 73 forks source link

IONIQ 5 Canada Region Timezone wrong #561

Closed Gandhi11 closed 3 months ago

Gandhi11 commented 5 months ago

Description

When retrieving vehicle information, the timezone is set to UTC but the car is in UTC-4 America/Toronto

What I Did

vm = VehicleManager(
    region=2,
    brand=2,
    username=XXXX,
    password=XXXX,
    pin=XXXX,
)

Output

vm.vehicles[KEY].timezone = UTC

Maybe I am missing something. Any help would be appreciated.

ZuinigeRijder commented 5 months ago

Note that in KiaUvoApiEU.py, the default timezone is set. This is not done in KiaUvoApiCA.py.

class KiaUvoApiEU(ApiImplType1):
    data_timezone = tz.gettz("Europe/Berlin")

But probably also the above is not correct too and should get/convert to the local timezone whenever a datetime is stored, as well for all other regions? At least that all users of the API do not have to convert to the local timezone themselves??

@cdnninja Do you have an opinion on this?

cdnninja commented 5 months ago

Depend on the api. Some respond with data all in one time zone. Others are specific to the account. So would need to know that.

ZuinigeRijder commented 5 months ago

@Gandhi11 The datetimes given back for you in Canada, are they correct? So if the UTC datetime was converted to UTC-4, would that be the correct datetime?

Gandhi11 commented 5 months ago

@ZuinigeRijder I guess so. Currently all the date returned by the API seems to be in UTC.

So it could be a good solution to handle the conversion on the application that is using the API which should always return the datetime in UTC.

So in my case, it could be converted in your package https://github.com/ZuinigeRijder/hyundai_kia_connect_monitor with a configuration?

Keep in mind that currently were are in daylight saving (EDT) so UTC-4 but sometime we are in eastern time (EST) which is UTC-5.

ZuinigeRijder commented 5 months ago

@Gandhi11 Depends how consistent the behavior is of hyundai_kia_connect_api. Are the date/times consistent? Currently it is not, sometimes it is UTC time and sometimes it is local time?

@cdnninja So the IONIQ 5 in Canada, UTC-4 America/Toronto, shows:

When I run the debug.py, the output is saying timezone=datetime.timezone.utc, in the vm.vehicles

         last_updated_at=datetime.datetime
        (
            2024,
             5,
             23,
             10,
             3,
             17,
             tzinfo=datetime.timezone.utc
        ),
         timezone=datetime.timezone.utc,

My IONIQ 5 in the Netherlands (UTC+2, Europe/Amsterdam) shows:

last_updated_at=datetime.datetime
        (
            2024,
             5,
             22,
             12,
             41,
             24,
             tzinfo=tzfile
            (
                'Europe/Berlin'
            )
        ),
         timezone=tzfile
        (
            'Europe/Berlin'
        ),

I do not know what UK users will get back, if this is returned in their local timezone? I would expect that hyundai_kia_connect_api converts all datetimes to the local timezones (the timezone of the computer where the script runs). So Canada should also convert to local timezone.

Another approach could be to always convert to UTC time, to have consistent behavior in hyundai_kia_connect_api, but then hyundai_kia_connect_monitor (and other uses of the hyundai_kia_connect_api) need to convert to local time.

ZuinigeRijder commented 5 months ago

I locally tried to solve it in hyundai_kia_connect_api.

Added to utils.py:

    def get_safe_local_datetime(date: datetime) -> datetime:
    if date is not None and date.tzinfo is not None:
        date = date.astimezone()
    return date

Changed Vehicle.py last_updated_at to private instance variables and getter/setter:

    _last_updated_at: datetime.datetime = None

    @property
    def last_updated_at(self):
        return self._last_updated_at

    @last_updated_at.setter
    def last_updated_at(self, value):
        self._last_updated_at = get_safe_local_datetime(value)

And:

    @location.setter
    def location(self, value):
        self._location_latitude = value[0]
        self._location_longitude = value[1]
        self._location_last_set_time = get_safe_local_datetime(value[2])

Looks like that is working, without changing the rest of the code.

cdnninja commented 5 months ago

The theory is that the time zone setting in the api is for parsing the source data. This allows the consumer of the api to display in local time zone. Now a bug could exist.

ZuinigeRijder commented 5 months ago

@cdnninja I understand that the Hyundai Bluelink and Kia Connect API behaves different in what timezone is returned, dependent of region. However, I would expect that hyundai_kia_connect_api tries to have an unified entry point/approach and that the consumer of the hyundai_kia_connect_api does not need to think about converting to a local datetime, because sometimes it is in local time and sometimes it is in UTC time.

Would you agree with the approach in my previous post? If yes, I can make a Pull Request.

cdnninja commented 5 months ago

I'm a bit lost. It is unified. One region returns all data in UTC so we store that with the object. The other one doesn't.

As such the client shouldn't need to worry as the time zone data comes with the time.

cdnninja commented 5 months ago

I haven't looked at code closely so maybe it is a bug? Using local time zone is risky as it if offset it will create calls from home assistant trying to get local data.

cdnninja commented 5 months ago

When printed in local time zone is the data what is expected? As in recently updated?

ZuinigeRijder commented 5 months ago

Yes, the client needs to worry, because it always needs to convert to local time, because UTC is not really an end-user datetime. My changes are converting the UTC timezone to the local timezone always. Which my changes, the datetimes are changed from:

last_updated_at=datetime.datetime
        (
            2024,
             5,
             22,
             12,
             41,
             24,
             tzinfo=tzfile
            (
                'Europe/Berlin'
            )
        ),
         timezone=tzfile
        (
            'Europe/Berlin'
        ),

into

 _last_updated_at=datetime.datetime
        (
            2024,
             5,
             22,
             12,
             41,
             24,
             tzinfo=datetime.timezone
            (
                datetime.timedelta
                (
                    seconds=7200
                ),
                 'West-Europa
                (
                    zomertijd
                )'
            )
        ),

So still timezone information is in the datetime. But if you display the datetime, it will show you the local datetime with the timezone indication, e.g.

Last updated at         : 2024-05-22 12:41:24+02:00

And for Canada, the timezone will not be UTC, but become UTC-4 "-04:00"

cdnninja commented 5 months ago

So on concept always store in local time?

I'm okay with that however offset can't be used. Since DTC. It would need to poll the system and convert based on that.

I don't have a Hyundai Kia anymore so hard for me to test. :)

ZuinigeRijder commented 5 months ago

Yes, the concept is to always convert to the local timezone. I do not understand the comment about DTC. The conversion takes care of DTC. But I do not see a problem, also not in comparison of delta times.

ZuinigeRijder commented 5 months ago

@Gandhi11 Can you confirm that my changes are working as expected for you? Are the results then correct thereafter?

Gandhi11 commented 5 months ago

@ZuinigeRijder After making your changes, I received this error

20240529 09:25:49: Exception: 'property' object has no attribute 'tzinfo'
Traceback (most recent call last):
  File "/volume1/Programs/hyundai_kia_connect_monitor/monitor.py", line 416, in handle_vehicles
    manager.check_and_refresh_token()
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 130, in check_and_refresh_token
    self.initialize()
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 76, in initialize
    vehicles = self.api.get_vehicles(self.token)
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/KiaUvoApiCA.py", line 164, in get_vehicles
    vehicle: Vehicle = Vehicle(
  File "<string>", line 23, in __init__
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/Vehicle.py", line 309, in last_updated_at
    self._last_updated_at = get_safe_local_datetime(value)
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/utils.py", line 8, in get_safe_local_datetime
    if date is not None and date.tzinfo is not None:
AttributeError: 'property' object has no attribute 'tzinfo'
ZuinigeRijder commented 5 months ago

@Gandhi11 Strange, for me it is working. Can you try this for the method in utils.py.

def get_safe_local_datetime(date: datetime) -> datetime:
    if date is not None:
        date = date.astimezone()
    return date
Gandhi11 commented 5 months ago

@ZuinigeRijder Now I get this error

20240529 09:53:00: Exception: 'property' object has no attribute 'astimezone'
Traceback (most recent call last):
  File "/volume1/Programs/hyundai_kia_connect_monitor/monitor.py", line 416, in handle_vehicles
    manager.check_and_refresh_token()
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 130, in check_and_refresh_token
    self.initialize()
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 76, in initialize
    vehicles = self.api.get_vehicles(self.token)
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/KiaUvoApiCA.py", line 164, in get_vehicles
    vehicle: Vehicle = Vehicle(
  File "<string>", line 23, in __init__
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/Vehicle.py", line 309, in last_updated_at
    self._last_updated_at = get_safe_local_datetime(value)
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/utils.py", line 9, in get_safe_local_datetime
    date = date.astimezone()
AttributeError: 'property' object has no attribute 'astimezone'
ZuinigeRijder commented 5 months ago

I am confused. Apparently in KiaUvoApiCA.py line 241 is constructing a datetime object without timezone info????

        vehicle.last_updated_at = parse_datetime(
            get_child_value(state, "status.lastStatusDate"), self.data_timezone
        )

Can you add a print statement for debugging purposes.....looks like a datetime object is given as parameter without timezone info???

def get_safe_local_datetime(date: datetime) -> datetime:
    print(f"get_safe_local_datetime: {date}")
    if date is not None and date.tzinfo is not None:
        date = date.astimezone()
    return date
ZuinigeRijder commented 5 months ago

@Gandhi11 I also made a small test script, because I do not understand why it goes wrong

Can you provide the output of the previous post AND also the output of following test script?

# == test_ca.py Author: Zuinige Rijder ========= # pylint:disable=bare-except
""" Simple Python3 script to tests ca """

import datetime
import re

def parse_datetime(value, timezone) -> datetime.datetime:
    """parse_datetime"""
    if value is None:
        return datetime.datetime(2000, 1, 1, tzinfo=timezone)

    value = value.replace("-", "").replace("T", "").replace(":", "").replace("Z", "")
    m = re.match(r"(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})", value)
    return datetime.datetime(
        year=int(m.group(1)),
        month=int(m.group(2)),
        day=int(m.group(3)),
        hour=int(m.group(4)),
        minute=int(m.group(5)),
        second=int(m.group(6)),
        tzinfo=timezone,
    )

def get_safe_local_datetime(date: datetime) -> datetime:
    """get safe local datetime"""
    print(f"get_safe_local_datetime: {date}")
    if date is not None and date.tzinfo is not None:
        date = date.astimezone()
    return date

now = datetime.datetime.now()
get_safe_local_datetime(now)

now2 = parse_datetime("2024-05-2921:33:44", datetime.timezone.utc)
get_safe_local_datetime(now2)

now3 = parse_datetime("2024-05-2921:33:44", None)
get_safe_local_datetime(now3)
cdnninja commented 5 months ago

I'm also open to a PR once this is understood.

Gandhi11 commented 5 months ago

@ZuinigeRijder Here's the results.

For the first output ran on 2024-05-30 21:20 EDT UTC -4 get_safe_local_datetime: 2024-05-31 01:20:03+00:00

For the output of the test script ran on 2024-05-30 21:22 EDT UTC -4.

get_safe_local_datetime: 2024-05-30 21:22:34.814350
get_safe_local_datetime: 2024-05-29 21:33:44+00:00
get_safe_local_datetime: 2024-05-29 21:33:44

Looks like the first print is the good one.

ZuinigeRijder commented 5 months ago

@Gandhi11 Now I am even more confused, so with the first run, running monitor:

For the first output ran on 2024-05-30 21:20 EDT UTC -4 get_safe_local_datetime: 2024-05-31 01:20:03+00:00

You did no longer get the exceptions????:

AttributeError: 'property' object has no attribute 'tzinfo'

and

AttributeError: 'property' object has no attribute 'astimezone'

Note that the test script was just to see what would be the output when running variants. So the second output would be the expected outcome.

Gandhi11 commented 5 months ago

@ZuinigeRijder I reverted the change before running the scripts.

Here's the output with the changes you wanted me to apply.

get_safe_local_datetime: <property object at 0x417b8a28>
20240531 08:09:31: Exception: 'property' object has no attribute 'tzinfo'
Traceback (most recent call last):
  File "/volume1/Programs/hyundai_kia_connect_monitor/monitor.py", line 416, in handle_vehicles
    manager.check_and_refresh_token()
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 130, in check_and_refresh_token
    self.initialize()
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 76, in initialize
    vehicles = self.api.get_vehicles(self.token)
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/KiaUvoApiCA.py", line 164, in get_vehicles
    vehicle: Vehicle = Vehicle(
  File "<string>", line 23, in __init__
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/Vehicle.py", line 309, in last_updated_at
    self._last_updated_at = get_safe_local_datetime(value)
  File "/volume1/Programs/hyundai_kia_connect_monitor/hyundai_kia_connect_api/utils.py", line 9, in get_safe_local_datetime
    if date is not None and date.tzinfo is not None:
AttributeError: 'property' object has no attribute 'tzinfo'

The date variable doesn't seems to be a date... get_safe_local_datetime: <property object at 0x417b8a28>

ZuinigeRijder commented 5 months ago

@Gandhi11 I still do not understand. Can you run my solution again, with the method get_safe_local_datetime in utils.py as:

def get_safe_local_datetime(date: datetime) -> datetime:
    """get safe local datetime"""
    print(f"get_safe_local_datetime: {date}")
    print(type(date))
    print(date)
    print("Attributes and methods of date:")
    print(dir(date))
    if date is not None and hasattr(date, "tzinfo") and date.tzinfo is not None:
        date = date.astimezone()
    return date
ZuinigeRijder commented 4 months ago

@Gandhi11 Did you not have time for testing what is in the previous message?

gyveri commented 3 months ago

I wonder if there have been any further developments on this topic. I managed to get monitor.py, summary.py and dailystats.py running on a Raspberry Pi. But I noticed that the results displayed in the Google Sheets spreadsheet did not make sense. I realized that this appeared to be because the time stamps represented UTC times, rather than Pacific Daylight times. I wrote a little auxiliary script that runs one minute after monitor.py. The script reads the datetime, longitude and latitude from the last row of monitor.csv, determines the time zone from the longitude and latitude and applies the appropriate correction to the datetime and then saves monitor.csv with the modified last row. After the auxiliary script runs, the results shown in the Google Sheets spreadsheet make sense. But I consider this to be somewhat of a kluge, and it would be nice if monitor.py were timezone aware when it is used outside Europe.

ZuinigeRijder commented 3 months ago

@gyveri Which region and which car do you have? Apparently @Gandhi11 is no longer reacting, to do some tests. I cannot do the tests, because I live in region Europe and there the local time is computed correctly. So if you are willing to do some tests, maybe we can fix this for e.g. Canada.

gyveri commented 3 months ago

I live in central California (Pacific time zone, currently on Daylight Saving Time UTC-7:00) and have a 2024 Hyundai Ioniq5. I'll be happy to do some tests to help fix this issue.

ZuinigeRijder commented 3 months ago

@gyveri Thanks for helping to test. I will repeat the changes I did locally on top of hyundai_kia_connect_api latest release v3.22.1

Added to utils.py:

def get_safe_local_datetime(date: datetime) -> datetime:
    """get safe local datetime"""
    print(f"get_safe_local_datetime: {date}")
    print(type(date))
    print(date)
    print("Attributes and methods of date:")
    print(dir(date))
    if date is not None and hasattr(date, "tzinfo") and date.tzinfo is not None:
        date = date.astimezone()
    return date

Note that this does some logging statements (print), so if it does not work I have more of a clue why it does not work. If it works, those print statements can be removed.

Changed Vehicle.py last_updated_at to private instance variables and getter/setter. For convenience I post the whole Vehicle.py version:

# pylint:disable=missing-class-docstring,missing-function-docstring,wildcard-import,unused-wildcard-import,invalid-name
"""Vehicle class"""

import logging
import datetime
import typing
from dataclasses import dataclass, field

from .utils import get_float, get_safe_local_datetime
from .const import DISTANCE_UNITS

_LOGGER = logging.getLogger(__name__)

@dataclass
class TripInfo:
    """Trip Info"""

    hhmmss: str = None  # will not be filled by summary
    drive_time: int = None
    idle_time: int = None
    distance: int = None
    avg_speed: float = None
    max_speed: int = None

@dataclass
class DayTripCounts:
    """Day trip counts"""

    yyyymmdd: str = None
    trip_count: int = None

@dataclass
class MonthTripInfo:
    """Month Trip Info"""

    yyyymm: str = None
    summary: TripInfo = None
    day_list: list[DayTripCounts] = field(default_factory=list)

@dataclass
class DayTripInfo:
    """Day Trip Info"""

    yyyymmdd: str = None
    summary: TripInfo = None
    trip_list: list[TripInfo] = field(default_factory=list)

@dataclass
class DailyDrivingStats:
    # energy stats are expressed in watthours (Wh)
    date: datetime.datetime = None
    total_consumed: int = None
    engine_consumption: int = None
    climate_consumption: int = None
    onboard_electronics_consumption: int = None
    battery_care_consumption: int = None
    regenerated_energy: int = None
    # distance is expressed in (I assume) whatever unit the vehicle is
    # configured in. KMs (rounded) in my case
    distance: int = None
    distance_unit = DISTANCE_UNITS[1]  # set to kms by default

@dataclass
class Vehicle:
    id: str = None
    name: str = None
    model: str = None
    registration_date: str = None
    year: int = None
    VIN: str = None
    key: str = None
    ccu_ccs2_protocol_support: int = None
    # Not part of the API, enabled in our library for scanning.
    enabled: bool = True

    # Shared (EV/PHEV/HEV/IC)
    # General
    _total_driving_range: float = None
    _total_driving_range_value: float = None
    _total_driving_range_unit: str = None

    _odometer: float = None
    _odometer_value: float = None
    _odometer_unit: str = None

    _geocode_address: str = None
    _geocode_name: str = None

    car_battery_percentage: int = None
    engine_is_running: bool = None

    _last_updated_at: datetime.datetime = None
    timezone: datetime.timezone = datetime.timezone.utc  # default UTC

    dtc_count: typing.Union[int, None] = None
    dtc_descriptions: typing.Union[dict, None] = None

    smart_key_battery_warning_is_on: bool = None
    washer_fluid_warning_is_on: bool = None
    brake_fluid_warning_is_on: bool = None

    # Climate
    _air_temperature: float = None
    _air_temperature_value: float = None
    _air_temperature_unit: str = None

    air_control_is_on: bool = None
    defrost_is_on: bool = None
    steering_wheel_heater_is_on: bool = None
    back_window_heater_is_on: bool = None
    side_mirror_heater_is_on: bool = None
    front_left_seat_status: str = None
    front_right_seat_status: str = None
    rear_left_seat_status: str = None
    rear_right_seat_status: str = None

    # Door Status
    is_locked: bool = None
    front_left_door_is_open: bool = None
    front_right_door_is_open: bool = None
    back_left_door_is_open: bool = None
    back_right_door_is_open: bool = None
    trunk_is_open: bool = None
    hood_is_open: bool = None

    # Window Status
    front_left_window_is_open: bool = None
    front_right_window_is_open: bool = None
    back_left_window_is_open: bool = None
    back_right_window_is_open: bool = None

    # Tire Pressure
    tire_pressure_all_warning_is_on: bool = None
    tire_pressure_rear_left_warning_is_on: bool = None
    tire_pressure_front_left_warning_is_on: bool = None
    tire_pressure_front_right_warning_is_on: bool = None
    tire_pressure_rear_right_warning_is_on: bool = None

    # Service Data
    _next_service_distance: float = None
    _next_service_distance_value: float = None
    _next_service_distance_unit: str = None
    _last_service_distance: float = None
    _last_service_distance_value: float = None
    _last_service_distance_unit: str = None

    # Location
    _location_latitude: float = None
    _location_longitude: float = None
    _location_last_set_time: datetime.datetime = None

    # EV fields (EV/PHEV)

    ev_charge_port_door_is_open: typing.Union[bool, None] = None

    ev_charge_limits_dc: typing.Union[int, None] = None
    ev_charge_limits_ac: typing.Union[int, None] = None
    ev_charging_current: typing.Union[int, None] = None  # Europe feature only
    ev_v2l_discharge_limit: typing.Union[int, None] = None

    # energy consumed and regenerated since the vehicle was paired with the account
    # (so not necessarily for the vehicle's lifetime)
    # expressed in watt-hours (Wh)
    total_power_consumed: float = None  # Europe feature only
    total_power_regenerated: float = None  # Europe feature only
    # energy consumed in the last ~30 days
    # expressed in watt-hours (Wh)
    power_consumption_30d: float = None  # Europe feature only

    # Europe feature only
    daily_stats: list[DailyDrivingStats] = field(default_factory=list)

    month_trip_info: MonthTripInfo = None  # Europe feature only
    day_trip_info: DayTripInfo = None  # Europe feature only

    ev_battery_percentage: int = None
    ev_battery_soh_percentage: int = None
    ev_battery_remain: int = None
    ev_battery_capacity: int = None
    ev_battery_is_charging: bool = None
    ev_battery_is_plugged_in: bool = None

    _ev_driving_range: float = None
    _ev_driving_range_value: float = None
    _ev_driving_range_unit: str = None

    _ev_estimated_current_charge_duration: int = None
    _ev_estimated_current_charge_duration_value: int = None
    _ev_estimated_current_charge_duration_unit: str = None

    _ev_estimated_fast_charge_duration: int = None
    _ev_estimated_fast_charge_duration_value: int = None
    _ev_estimated_fast_charge_duration_unit: str = None

    _ev_estimated_portable_charge_duration: int = None
    _ev_estimated_portable_charge_duration_value: int = None
    _ev_estimated_portable_charge_duration_unit: str = None

    _ev_estimated_station_charge_duration: int = None
    _ev_estimated_station_charge_duration_value: int = None
    _ev_estimated_station_charge_duration_unit: str = None

    _ev_target_range_charge_AC: typing.Union[float, None] = None
    _ev_target_range_charge_AC_value: typing.Union[float, None] = None
    _ev_target_range_charge_AC_unit: typing.Union[str, None] = None

    _ev_target_range_charge_DC: typing.Union[float, None] = None
    _ev_target_range_charge_DC_value: typing.Union[float, None] = None
    _ev_target_range_charge_DC_unit: typing.Union[str, None] = None

    ev_first_departure_enabled: typing.Union[bool, None] = None
    ev_second_departure_enabled: typing.Union[bool, None] = None

    ev_first_departure_days: typing.Union[list, None] = None
    ev_second_departure_days: typing.Union[list, None] = None

    ev_first_departure_time: typing.Union[datetime.time, None] = None
    ev_second_departure_time: typing.Union[datetime.time, None] = None

    ev_first_departure_climate_enabled: typing.Union[bool, None] = None
    ev_second_departure_climate_enabled: typing.Union[bool, None] = None

    _ev_first_departure_climate_temperature: typing.Union[float, None] = None
    _ev_first_departure_climate_temperature_value: typing.Union[float, None] = None
    _ev_first_departure_climate_temperature_unit: typing.Union[str, None] = None

    _ev_second_departure_climate_temperature: typing.Union[float, None] = None
    _ev_second_departure_climate_temperature_value: typing.Union[float, None] = None
    _ev_second_departure_climate_temperature_unit: typing.Union[str, None] = None

    ev_first_departure_climate_defrost: typing.Union[bool, None] = None
    ev_second_departure_climate_defrost: typing.Union[bool, None] = None

    ev_off_peak_start_time: typing.Union[datetime.time, None] = None
    ev_off_peak_end_time: typing.Union[datetime.time, None] = None
    ev_off_peak_charge_only_enabled: typing.Union[bool, None] = None

    ev_schedule_charge_enabled: typing.Union[bool, None] = None

    # IC fields (PHEV/HEV/IC)
    _fuel_driving_range: float = None
    _fuel_driving_range_value: float = None
    _fuel_driving_range_unit: str = None
    fuel_level: float = None

    fuel_level_is_low: bool = None

    # Calculated fields
    engine_type: str = None

    # Debug fields
    data: dict = None

    @property
    def geocode(self):
        return self._geocode_name, self._geocode_address

    @geocode.setter
    def geocode(self, value):
        self._geocode_name = value[0]
        self._geocode_address = value[1]

    @property
    def total_driving_range(self):
        return self._total_driving_range

    @property
    def total_driving_range_unit(self):
        return self._total_driving_range_unit

    @total_driving_range.setter
    def total_driving_range(self, value):
        self._total_driving_range_value = value[0]
        self._total_driving_range_unit = value[1]
        self._total_driving_range = value[0]

    @property
    def next_service_distance(self):
        return self._next_service_distance

    @next_service_distance.setter
    def next_service_distance(self, value):
        self._next_service_distance_value = value[0]
        self._next_service_distance_unit = value[1]
        self._next_service_distance = value[0]

    @property
    def last_service_distance(self):
        return self._last_service_distance

    @last_service_distance.setter
    def last_service_distance(self, value):
        self._last_service_distance_value = value[0]
        self._last_service_distance_unit = value[1]
        self._last_service_distance = value[0]

    @property
    def last_updated_at(self):
        return self._last_updated_at

    @last_updated_at.setter
    def last_updated_at(self, value):
        self._last_updated_at = get_safe_local_datetime(value)

    @property
    def location_latitude(self):
        return self._location_latitude

    @property
    def location_longitude(self):
        return self._location_longitude

    @property
    def location(self):
        return self._location_longitude, self._location_latitude

    @property
    def location_last_updated_at(self):
        """
        return last location datetime.
        last_updated_at and location_last_updated_at can be different.
        The newest of those 2 can be computed by the caller.
        """
        return self._location_last_set_time

    @location.setter
    def location(self, value):
        self._location_latitude = value[0]
        self._location_longitude = value[1]
        self._location_last_set_time = get_safe_local_datetime(value[2])

    @property
    def odometer(self):
        return self._odometer

    @property
    def odometer_unit(self):
        return self._odometer_unit

    @odometer.setter
    def odometer(self, value):
        float_value = get_float(value[0])
        self._odometer_value = float_value
        self._odometer_unit = value[1]
        self._odometer = float_value

    @property
    def air_temperature(self):
        return self._air_temperature

    @air_temperature.setter
    def air_temperature(self, value):
        self._air_temperature_value = value[0]
        self._air_temperature_unit = value[1]
        self._air_temperature = value[0]

    @property
    def ev_driving_range(self):
        return self._ev_driving_range

    @property
    def ev_driving_range_unit(self):
        return self._ev_driving_range_unit

    @ev_driving_range.setter
    def ev_driving_range(self, value):
        self._ev_driving_range_value = value[0]
        self._ev_driving_range_unit = value[1]
        self._ev_driving_range = value[0]

    @property
    def ev_estimated_current_charge_duration(self):
        return self._ev_estimated_current_charge_duration

    @ev_estimated_current_charge_duration.setter
    def ev_estimated_current_charge_duration(self, value):
        self._ev_estimated_current_charge_duration_value = value[0]
        self._ev_estimated_current_charge_duration_unit = value[1]
        self._ev_estimated_current_charge_duration = value[0]

    @property
    def ev_estimated_fast_charge_duration(self):
        return self._ev_estimated_fast_charge_duration

    @ev_estimated_fast_charge_duration.setter
    def ev_estimated_fast_charge_duration(self, value):
        self._ev_estimated_fast_charge_duration_value = value[0]
        self._ev_estimated_fast_charge_duration_unit = value[1]
        self._ev_estimated_fast_charge_duration = value[0]

    @property
    def ev_estimated_portable_charge_duration(self):
        return self._ev_estimated_portable_charge_duration

    @ev_estimated_portable_charge_duration.setter
    def ev_estimated_portable_charge_duration(self, value):
        self._ev_estimated_portable_charge_duration_value = value[0]
        self._ev_estimated_portable_charge_duration_unit = value[1]
        self._ev_estimated_portable_charge_duration = value[0]

    @property
    def ev_estimated_station_charge_duration(self):
        return self._ev_estimated_station_charge_duration

    @ev_estimated_station_charge_duration.setter
    def ev_estimated_station_charge_duration(self, value):
        self._ev_estimated_station_charge_duration_value = value[0]
        self._ev_estimated_station_charge_duration_unit = value[1]
        self._ev_estimated_station_charge_duration = value[0]

    @property
    def ev_target_range_charge_AC(self):
        return self._ev_target_range_charge_AC

    @property
    def ev_target_range_charge_AC_unit(self):
        return self._ev_target_range_charge_AC_unit

    @ev_target_range_charge_AC.setter
    def ev_target_range_charge_AC(self, value):
        self._ev_target_range_charge_AC_value = value[0]
        self._ev_target_range_charge_AC_unit = value[1]
        self._ev_target_range_charge_AC = value[0]

    @property
    def ev_target_range_charge_DC(self):
        return self._ev_target_range_charge_DC

    @property
    def ev_target_range_charge_DC_unit(self):
        return self._ev_target_range_charge_DC_unit

    @ev_target_range_charge_DC.setter
    def ev_target_range_charge_DC(self, value):
        self._ev_target_range_charge_DC_value = value[0]
        self._ev_target_range_charge_DC_unit = value[1]
        self._ev_target_range_charge_DC = value[0]

    @property
    def ev_first_departure_climate_temperature(self):
        return self._ev_first_departure_climate_temperature

    @property
    def ev_first_departure_climate_temperature_unit(self):
        return self._ev_first_departure_climate_temperature_unit

    @ev_first_departure_climate_temperature.setter
    def ev_first_departure_climate_temperature(self, value):
        self._ev_first_departure_climate_temperature_value = value[0]
        self._ev_first_departure_climate_temperature_unit = value[1]
        self._ev_first_departure_climate_temperature = value[0]

    @property
    def ev_second_departure_climate_temperature(self):
        return self._ev_second_departure_climate_temperature

    @property
    def ev_second_departure_climate_temperature_unit(self):
        return self._ev_second_departure_climate_temperature_unit

    @ev_second_departure_climate_temperature.setter
    def ev_second_departure_climate_temperature(self, value):
        self._ev_second_departure_climate_temperature_value = value[0]
        self._ev_second_departure_climate_temperature_unit = value[1]
        self._ev_second_departure_climate_temperature = value[0]

    @property
    def fuel_driving_range(self):
        return self._fuel_driving_range

    @fuel_driving_range.setter
    def fuel_driving_range(self, value):
        self._fuel_driving_range_value = value[0]
        self._fuel_driving_range_unit = value[1]
        self._fuel_driving_range = value[0]
gyveri commented 3 months ago

-Not sure what's happening here. On a new installation, I updated the .api file, ran monitor.py and got the result below. I reverted to the prior, unmodified .api and got the same result. I also checked the version that's been running happily every hour on another machine and that's giving access token errors as well. It stopped working after the 03:00 PDT run today. Checked the Hyundai app and web site - both say they're having technical problems. Should have checked these first. I'll try again tomorrow.

pi@Pi5-1:~/hyundai_kia_connect_monitor $ python monitor.py 20240716 17:30:29: Exception: 'access_token'I reverted to the prior, unmodified .api and got the same result. I also checked the version that's been running happily every hour on another machine and that's giving access token errors as well. It stopped working after the 03:00 PDT run today. Checked the Hyundai app and web site - both say they're having technical problems. I'll try again tomorrow. Traceback (most recent call last): File "/home/pi/hyundai_kia_connect_monitor/monitor.py", line 416, in handle_vehicles manager.check_and_refresh_token() File "/home/pi/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 130, in check_and_refresh_token self.initialize() File "/home/pi/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 74, in initialize self.token: Token = self.api.login(self.username, self.password) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/hyundai_kia_connect_monitor/hyundai_kia_connect_api/HyundaiBlueLinkAPIUSA.py", line 127, in login access_token = response["access_token"]


KeyError: 'access_token'
20240716 17:30:29: Sleeping a minute
20240716 17:31:30: Exception: 'access_token'
Traceback (most recent call last):
  File "/home/pi/hyundai_kia_connect_monitor/monitor.py", line 416, in handle_vehicles
    manager.check_and_refresh_token()
  File "/home/pi/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 130, in check_and_refresh_token
    self.initialize()
  File "/home/pi/hyundai_kia_connect_monitor/hyundai_kia_connect_api/VehicleManager.py", line 74, in initialize
    self.token: Token = self.api.login(self.username, self.password)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/pi/hyundai_kia_connect_monitor/hyundai_kia_connect_api/HyundaiBlueLinkAPIUSA.py", line 127, in login
    access_token = response["access_token"]
                   ~~~~~~~~^^^^^^^^^^^^^^^^
KeyError: 'access_token'
ZuinigeRijder commented 3 months ago

@gyveri Bummer. Hope that the technical problems of Hyundai are solved soon.

gyveri commented 3 months ago

It looks like our recent messages have disappeared from this thread. The Hyundai network seems to be back. My hourly running system started giving results at 00:00 today PDT.

I downloaded and installed the updated .api and edited to make the addition to utils.py and replace Vehicle.py . I then ran monitor.py . There were no error messages and it printed the following:

get_safe_local_datetime: 2024-07-17 18:00:27+00:00 <class 'datetime.datetime'> 2024-07-17 18:00:27+00:00 Attributes and methods of date: ['add', 'class', 'delattr', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getstate', 'gt', 'hash', 'init', 'init_subclass', 'le', 'lt', 'ne', 'new', 'radd', 'reduce', 'reduce_ex', 'repr', 'rsub', 'setattr', 'sizeof', 'str', 'sub', 'subclasshook', 'astimezone', 'combine', 'ctime', 'date', 'day', 'dst', 'fold', 'fromisocalendar', 'fromisoformat', 'fromordinal', 'fromtimestamp', 'hour', 'isocalendar', 'isoformat', 'isoweekday', 'max', 'microsecond', 'min', 'minute', 'month', 'now', 'replace', 'resolution', 'second', 'strftime', 'strptime', 'time', 'timestamp', 'timetuple', 'timetz', 'today', 'toordinal', 'tzinfo', 'tzname', 'utcfromtimestamp', 'utcnow', 'utcoffset', 'utctimetuple', 'weekday', 'year'] get_safe_local_datetime: 2024-07-17 18:09:59+00:00 <class 'datetime.datetime'> 2024-07-17 18:09:59+00:00 Attributes and methods of date: ['add', 'class', 'delattr', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getstate', 'gt', 'hash', 'init', 'init_subclass', 'le', 'lt', 'ne', 'new', 'radd', 'reduce', 'reduce_ex', 'repr', 'rsub', 'setattr', 'sizeof', 'str', 'sub', 'subclasshook', 'astimezone', 'combine', 'ctime', 'date', 'day', 'dst', 'fold', 'fromisocalendar', 'fromisoformat', 'fromordinal', 'fromtimestamp', 'hour', 'isocalendar', 'isoformat', 'isoweekday', 'max', 'microsecond', 'min', 'minute', 'month', 'now', 'replace', 'resolution', 'second', 'strftime', 'strptime', 'time', 'timestamp', 'timetuple', 'timetz', 'today', 'toordinal', 'tzinfo', 'tzname', 'utcfromtimestamp', 'utcnow', 'utcoffset', 'utctimetuple', 'weekday', 'year']

In the above, I noticed that "print(f"get_safe_local_datetime: {date}")" appears to print the current UTC datetime - date doesn't get localized until the next-but-last line.

monitor.csv now contains the following (location details removed): datetime, longitude, latitude, engineOn, 12V%, odometer, SOC%, charging, plugged, address, EV range 2024-07-17 11:09:59-07:00, -121, 36, False, 81, 3256, 80, False, 2, [address details] ; California; United States, 0 The datetime information now looks correct for my location.

What next?

gyveri commented 3 months ago

Ignore my comment regarding the recent messages - they reappeared after I posted my comment.

gyveri commented 3 months ago

I have now installed the modified API in my hourly machine and it appears to be running OK. I have disabled my datetime localization script since the latest entry in monitor.csv now has the correct datetime for my location.

ZuinigeRijder commented 3 months ago

@gyveri Closed, as this is now part of Release v3.22.2 https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/releases/tag/v3.22.2