tache / homeassistant-kidde

A custom component for Kidde HomeSafe.
MIT License
15 stars 3 forks source link

BUG: Improve string to datetime compatability #8

Closed carterbox closed 1 month ago

carterbox commented 1 month ago

Improve compatability for string to datetime conversion for timestamp sensors by changing how the datetime string from the kidde api is converted.

@agarkauskas, please seee if this works for your devices?

Closes #7

agarkauskas commented 1 month ago

Thanks @carterbox ! Still problematic but moving forward! Forgive my ignorance in Home Assistant, I am still learning. This is an extract of my "grepped" log file:

2024-06-23 18:53:14.420 DEBUG (MainThread) [custom_components.kidde.coordinator] Finished fetching kidde data in 0.291 seconds (success: True)
2024-06-23 18:53:14.422 DEBUG (MainThread) [custom_components.kidde.sensor] last_seen of type <class 'str'> is 2024-06-23T14:02:19.734902169Z
2024-06-23 18:53:14.422 DEBUG (MainThread) [custom_components.kidde.sensor] last_test_time of type <class 'NoneType'> is None
2024-06-23 18:53:14.422 DEBUG (MainThread) [custom_components.kidde.sensor] smoke_level of type <class 'int'> is 4
2024-06-23 18:53:14.422 DEBUG (MainThread) [custom_components.kidde.sensor] co_level of type <class 'int'> is 0
2024-06-23 18:53:14.422 DEBUG (MainThread) [custom_components.kidde.sensor] battery_state of type <class 'str'> is ok
2024-06-23 18:53:14.422 DEBUG (MainThread) [custom_components.kidde.sensor] life of type <class 'int'> is 535
2024-06-23 18:53:14.423 DEBUG (MainThread) [custom_components.kidde.sensor] last_seen of type <class 'str'> is 2024-06-23T22:09:43.999904681Z
2024-06-23 18:53:14.423 DEBUG (MainThread) [custom_components.kidde.sensor] last_test_time of type <class 'NoneType'> is None
2024-06-23 18:53:14.423 DEBUG (MainThread) [custom_components.kidde.sensor] smoke_level of type <class 'int'> is 3
2024-06-23 18:53:14.423 DEBUG (MainThread) [custom_components.kidde.sensor] co_level of type <class 'int'> is 0
2024-06-23 18:53:14.423 DEBUG (MainThread) [custom_components.kidde.sensor] battery_state of type <class 'str'> is ok
2024-06-23 18:53:14.423 DEBUG (MainThread) [custom_components.kidde.sensor] life of type <class 'int'> is 535
2024-06-23 18:53:14.423 DEBUG (MainThread) [custom_components.kidde.sensor] last_seen of type <class 'str'> is 2024-06-23T22:36:49.985605162Z
2024-06-23 18:53:14.424 DEBUG (MainThread) [custom_components.kidde.sensor] last_test_time of type <class 'NoneType'> is None
2024-06-23 18:53:14.424 DEBUG (MainThread) [custom_components.kidde.sensor] smoke_level of type <class 'int'> is 0
2024-06-23 18:53:14.424 DEBUG (MainThread) [custom_components.kidde.sensor] co_level of type <class 'int'> is 0
2024-06-23 18:53:14.424 DEBUG (MainThread) [custom_components.kidde.sensor] battery_state of type <class 'str'> is ok
2024-06-23 18:53:14.424 DEBUG (MainThread) [custom_components.kidde.sensor] life of type <class 'int'> is 535
2024-06-23 18:53:14.424 DEBUG (MainThread) [custom_components.kidde.sensor] last_seen of type <class 'str'> is 2024-06-23T12:43:32.26698786Z
2024-06-23 18:53:14.424 DEBUG (MainThread) [custom_components.kidde.sensor] last_test_time of type <class 'str'> is 2024-06-22T16:00:19Z
  File "/config/custom_components/kidde/sensor.py", line 166, in native_value

The new code created this problem:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 255, in _handle_refresh_interval
    await self._async_refresh(log_failures=True, scheduled=True)
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 411, in _async_refresh
    self.async_update_listeners()
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 165, in async_update_listeners
    update_callback()
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 491, in _handle_coordinator_update
    self.async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1000, in async_write_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1126, in _async_write_ha_state
    state, attr, capabilities, shadowed_attr = self.__async_calculate_state()
                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1061, in __async_calculate_state
    state = self._stringify_state(available)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1006, in _stringify_state
    if (state := self.state) is None:
                 ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/sensor/__init__.py", line 543, in state
    value = self.native_value
            ^^^^^^^^^^^^^^^^^
  File "/config/custom_components/kidde/sensor.py", line 166, in native_value
    leading, fractions_of_seconds = value.split('.')
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: not enough values to unpack (expected 2, got 1)
agarkauskas commented 1 month ago

This is my first python trial:

>>> value = '2024-06-22T16:00:19Z'
>>> leading, fractions_of_seconds = value.split('.')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 2, got 1)

So the issue is that there is no decimals to split. The solution would be

IF isThereSecondsDecimals()
   split them as it is today
ELSE
   get only the seconds and smash 0 on seconds decimals

But I dont know how to write that in python. Yet.

agarkauskas commented 1 month ago

Ok, it is ugly. But it works.

class KiddeSensorTimestampEntity(KiddeEntity, SensorEntity):
    """A KiddeSensoryEntity which returns a datetime.

    Assume sensor returns datetime string e.g. '2024-06-14T03:40:39.667544824Z'
    agarkauskas 20240623 - Kidde datetime might be '2024-06-22T16:00:19Z'
    which needs to be converted to a python datetime.
    """

    @property
    def native_value(self) -> datetime.datetime | None:
        """Return the native value of the sensor."""
        value = self.kidde_device.get(self.entity_description.key)
        dtype = type(value)
        logger.debug(f"{self.entity_description.key} of type {dtype} is {value}")
        if value is None:
            return value
        # Different detector models return different precision for time, so we
        # need to strip anything beyond microseconds
        # https://github.com/tache/homeassistant-kidde/issues/7
        whereIsTheDot = value.find('.')
        if whereIsTheDot != -1:
            leading, fractions_of_seconds = value.split('.')
        else:
            leading = value[:-1]
            fractions_of_seconds = '000000'
        stripped = f"{leading}.{fractions_of_seconds[:6]}"
        return datetime.datetime.strptime(stripped, "%Y-%m-%dT%H:%M:%S.%f").replace(
            tzinfo=datetime.timezone.utc
        )
carterbox commented 1 month ago

Thanks, @agarkauskas for reporting! Based on your debug logs, I'm pretty sure we handle both types of datetime strings now. Comment here if that's not the case.