TheFes / cheapest-energy-hours

Jinja macro to find the cheapest energy prices
GNU General Public License v3.0
60 stars 7 forks source link

Issues in HA 2024.7.3 due to timezone/non-timezone comparison and datetime/str comparison (Tibber) #147

Closed piwi91 closed 1 month ago

piwi91 commented 1 month ago

There is an issue with timezone/non-timezone comparisons and string/datetime comparison. I can't exactly find what's causing the issue but I already changed some things.

Trace 1:

Trigger Update Coordinator: Error executing script. Unexpected error for call_service at pos 1: can't compare offset-naive and offset-aware datetimes
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 527, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 764, in _async_call_service_step
    response_data = await self._async_run_long_action(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 727, in _async_run_long_action
    return await long_task
           ^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2731, in async_call
    response_data = await coro
                    ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2774, in _execute_service
    return await target(service_call)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/tibber/services.py", line 66, in __get_prices
    if price["start_time"].replace(tzinfo=None) >= start
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: can't compare offset-naive and offset-aware datetimes

This was due to non-timezone/timezone comparison. I fixed this by using the template configuration below (note the strftime in data start and end):

  - trigger:
      - platform: time_pattern
        hours: "/1"
      - platform: homeassistant
        event: start
    action:
      - service: tibber.get_prices
        data:
          start: "{{ (today_at() - timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S') }}"
          end: "{{ (today_at() + timedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S') }}"
        response_variable: prices
    sensor:
      - unique_id: 79c470d8-4ccd-4f44-b3a2-e3d59d5dda8a
        name: Tibber prices
        state: "{{ prices.prices.values() | first | selectattr('start_time', '<=', utcnow()) | map(attribute='price') | list | last }}"
        attributes:
          prices: "{{ prices.prices.values() | first }}"

The I got the next issue, Error rendering state template for sensor.tibber_prices: TypeError: '<=' not supported between instances of 'datetime.datetime' and 'str', to fix this I removed the strftime from the selectattr (already fixed in above configuration). Issue resides bceause the prices attribute has datetime objects instead of strings which cause more issues in the second step. Any idea how to fix?

Output of the sensor:

prices: >-
  [{'start_time': datetime.datetime(2024, 7, 23, 0, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2531,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 1, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2419,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 2, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2416,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 3, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2353,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 4, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2382,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 5, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.242,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 6, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2737,
  'level': 'EXPENSIVE'}, {'start_time': datetime.datetime(2024, 7, 23, 7, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2932,
  'level': 'EXPENSIVE'}, {'start_time': datetime.datetime(2024, 7, 23, 8, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2865,
  'level': 'EXPENSIVE'}, {'start_time': datetime.datetime(2024, 7, 23, 9, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2575,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 10, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2362,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 11, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2232,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 12, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.197,
  'level': 'CHEAP'}, {'start_time': datetime.datetime(2024, 7, 23, 13, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.1921,
  'level': 'CHEAP'}, {'start_time': datetime.datetime(2024, 7, 23, 14, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.1883,
  'level': 'CHEAP'}, {'start_time': datetime.datetime(2024, 7, 23, 15, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.1924,
  'level': 'CHEAP'}, {'start_time': datetime.datetime(2024, 7, 23, 16, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2087,
  'level': 'CHEAP'}, {'start_time': datetime.datetime(2024, 7, 23, 17, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2441,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 18, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2525,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 19, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.269,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 20, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2943,
  'level': 'EXPENSIVE'}, {'start_time': datetime.datetime(2024, 7, 23, 21, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2763,
  'level': 'EXPENSIVE'}, {'start_time': datetime.datetime(2024, 7, 23, 22, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2742,
  'level': 'NORMAL'}, {'start_time': datetime.datetime(2024, 7, 23, 23, 0,
  tzinfo=datetime.timezone(datetime.timedelta(seconds=7200))), 'price': 0.2638,
  'level': 'NORMAL'}]
friendly_name: Tibber prices
TheFes commented 1 month ago

I don't have Tibber myself so I made some assumptions based on the other service calls for energy prices (like for Energy Zero). In those service calls datetime strings are used, so I assumed the same here.

I would expect that datetime objects are allowed as input for the service call, if not, it might be worthwhile to write up an issue about that.

To fix the last issue, a for loop is needed to convert all datetime objects to strings. I will adjust the docs on this

TheFes commented 1 month ago

Can you try this

  - trigger:
      - platform: time_pattern
        hours: "/1"
      - platform: homeassistant
        event: start
    action:
      - service: tibber.get_prices
        data:
          start: "{{ (today_at() - timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S') }}"
          end: "{{ (today_at() + timedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S') }}"
        response_variable: prices
    sensor:
      - unique_id: 79c470d8-4ccd-4f44-b3a2-e3d59d5dda8a
        name: Tibber prices
        state: "{{ prices.prices.values() | first | selectattr('start_time', '<=', now()) | map(attribute='price') | list | last }}"
        attributes:
          prices: >
            {% set ns = namespace(prices=[]) %}
            {% for i in prices.prices.values() | first %}
              {% set n = dict(start_time = i.start_time.isoformat(), price = i.price) %}
              {% set ns.prices = ns.prices + [n] %}
            {% endfor %}
            {{ ns.prices }}
piwi91 commented 1 month ago

Thanks @TheFes , that fixed it! :)