Todoist Support

So there are actually 3 files now to make everything work. You can see them all in the root directory of the Calendar platform; anything modified within the last couple weeks has my changes in it.

  • todoist.py, which is what you already have. You should be able to copy-paste this into custom_components/calendar.

  • The calendar initialization file. I had to change this yesterday because it wasn’t validating the config properly. I think this is where your problem is, but I’m not sure if copying it over into custom_components/calendar will fix things. It should, but I don’t know how HASS handles it internally. It’s worth a try.

  • The Services description. This is just an example of the service call for the front end, it should work fine without it.

Worst-case scenario, if nothing works, try changing this:

from homeassistant.components.calendar import (
    CalendarEventDevice, PLATFORM_SCHEMA)

to this:

from homeassistant.components.calendar import CalendarEventDevice
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA  # noqa

Note that if your config is invalid, it won’t throw any errors at you. It’ll just crash.

If it still doesn’t work, can you paste the exact message you’re getting?

Got it working after I replaced these 2 lines and added the other 2 files. Thanks again!!

The pull request got accepted and merged! It’ll officially be part of HASS as of 0.54.

Note that you’d need to delete the custom_components/calendar directory once 0.54 hits to actually have the latest changes.

1 Like

Thanks @Jay2645 for all your hard work on this!!

There is a bug though, if you have tasks with no duedates, the projects never gets updated, and fills the log with:

2017-09-24 12:27:40 ERROR (MainThread) [homeassistant.helpers.entity] Update for calendar.handla fails
Traceback (most recent call last):
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/helpers/entity.py", line 225, in async_update_ha_state
    yield from self.hass.async_add_job(self.update)
  File "/usr/lib/python3.5/asyncio/futures.py", line 380, in __iter__
    yield self  # This tells Task to wait for completion.
  File "/usr/lib/python3.5/asyncio/tasks.py", line 304, in _wakeup
    future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 293, in result
    raise self._exception
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/components/calendar/todoist.py", line 246, in update
    super().update()
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/components/calendar/__init__.py", line 138, in update
    if not self.data or not self.data.update():
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/util/__init__.py", line 306, in wrapper
    result = method(*args, **kwargs)
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/components/calendar/todoist.py", line 528, in update
    DATETIME: self.event[START].strftime(DATE_STR_FORMAT)
AttributeError: 'dict' object has no attribute 'strftime'
1 Like

Can I get the tasklist to show in a formatted way?

I tried with template but it would just get the entire string

@hoberion Here’s what I do to get it to speak my tasks in the morning. Note, I used the following on my sensors to reverse the order. I like to hear the last to do I added first:

  todoist_1:
    friendly_name: 'Task 1'
    value_template: >-
      {% set tasknum = ((states.calendar.inbox.attributes.all_tasks | length | int) - 1)%}
      {% set task = states.calendar.inbox.attributes.all_tasks[tasknum] %}
      {{task}}
  todoist_2:
    friendly_name: 'Task 2'
    value_template: >-
      {% set tasknum = ((states.calendar.inbox.attributes.all_tasks | length | int) - 2)%}
      {% set task = states.calendar.inbox.attributes.all_tasks[tasknum] %}
      {{task}}
  todoist_3:
    friendly_name: 'Task 3'
    value_template: >-
      {% set tasknum = ((states.calendar.inbox.attributes.all_tasks | length | int) - 3)%}
      {% set task = states.calendar.inbox.attributes.all_tasks[tasknum] %}
      {{task}}
  todoist_4:
    friendly_name: 'Task 4'
    value_template: >-
      {% set tasknum = ((states.calendar.inbox.attributes.all_tasks | length | int) - 4)%}
      {% set task = states.calendar.inbox.attributes.all_tasks[tasknum] %}
      {{task}}
  todoist_5:
    friendly_name: 'Task 5'
    value_template: >-
      {% set tasknum = ((states.calendar.inbox.attributes.all_tasks | length | int) - 5)%}
      {% set task = states.calendar.inbox.attributes.all_tasks[tasknum] %}
      {{task}}
  todoist_count:
    value_template: "{{ states.calendar.inbox.attributes.all_tasks|length|int}}"
  todoist_all:
    value_template: >-
      {% set todo1 = states.sensor.todoist_1.state %}
      {% set todo2 = states.sensor.todoist_2.state %}
      {% set todo3 = states.sensor.todoist_3.state %}
      {% set todo4 = states.sensor.todoist_4.state %}
      {% set todo5 = states.sensor.todoist_5.state %}
      {% set count = states.calendar.inbox.attributes.all_tasks|length|int%}
      {% set join1 = '. and also remember to'%}
      {% set join2 = '. Finally. You need to'%}
      {% set pause = '.'%}
      {% set opening1 = 'You have'%}
      {% set opening2 = 'reminders.'%}
      {% set opening2_1 = 'reminder.'%}
      {% set extra_count = (count - 5) | int %}
      {% set finish = '. You also have '%}
      {% set other = ' other '%}
      {% if count > 6 %}
        {{ [opening1, count, opening2, todo1, pause, todo2, pause, todo3, join1, todo4, join2, todo5, finish, extra_count, other, opening2]|join(' ') }}
      {% elif count == 6 %}
        {{ [opening1, count, opening2, todo1, pause, todo2, pause, todo3, pause, todo4, join2, todo5, finish, extra_count, other, opening2_1]|join(' ') }}
      {% elif count == 5 %}
        {{ [opening1, count, opening2, todo1, pause, todo2, pause, todo3, join1, todo4, join2, todo5]|join(' ') }}
      {% elif count == 4 %}
        {{ [opening1, count, opening2, todo1, pause, todo2, pause, todo3, join2, todo4]|join(' ') }}
      {% elif count > 3 %}
        {{ [opening1, count, opening2, todo1, pause, todo2, join2, todo3]|join(' ') }}
      {% elif count == 3 %}
        {{ [opening1, count, opening2, todo1, join1, todo2, join1, todo3]|join(' ') }}
      {% elif count == 2 %}
        {{ [opening1, count, opening2, todo1, join2, todo2]|join(' ') }}
      {% elif count == 1 %}
        {{ opening1, count, opening_2_1, todo1 }}
      {% else %}''
      {% endif %}

I noticed the same problem. My tasks don’t update if there’s no date

same here:

 2017-09-29 12:02:40 ERROR (MainThread) [homeassistant.helpers.entity] Update for calendar.algemeen fails
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 225, in async_update_ha_state
    yield from self.hass.async_add_job(self.update)
  File "/usr/lib/python3.6/asyncio/futures.py", line 331, in __iter__
    yield self  # This tells Task to wait for completion.
  File "/usr/lib/python3.6/asyncio/tasks.py", line 244, in _wakeup
    future.result()
  File "/usr/lib/python3.6/asyncio/futures.py", line 244, in result
    raise self._exception
  File "/usr/lib/python3.6/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3.6/site-packages/homeassistant/components/calendar/todoist.py", line 246, in update
    super().update()
  File "/usr/lib/python3.6/site-packages/homeassistant/components/calendar/__init__.py", line 138, in update
    if not self.data or not self.data.update():
  File "/usr/lib/python3.6/site-packages/homeassistant/util/__init__.py", line 306, in wrapper
    result = method(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/homeassistant/components/calendar/todoist.py", line 528, in update
    DATETIME: self.event[START].strftime(DATE_STR_FORMAT)
AttributeError: 'dict' object has no attribute 'strftime'

ok, this is odd, deleted all the tasks with no date (including archived) and I am still getting this error.

the first time it starts (hass restart) it gets the correct entries but fails when updating

Hi,

It seems possible to use the service in an automation rule in order to create a new task in ToDoist. How can I create the “service json payload” in my automation rule ?

Is someone have any example ?

Get the same behavior.

@Jay2645 Is there a fix coming for the update issue described above?

@Jay2645 I can’t get todoist to load after 0.57.3. Seems like something broke.

I’ll take a look once I get a chance. It’s peak season at work presently, and I’m slammed with a bunch of school projects on top of that, so I can’t make any guarantees as to a timeline.

I’m glad you guys were able to figure out what was causing that other error, however; it was reported as an issue on GitHub and I wasn’t able to replicate it.

No worries, whenever you get time. Here’s the error it’s generating:

Error while setting up platform todoist
Traceback (most recent call last):
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py”, line 601, in urlopen
chunked=chunked)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py”, line 346, in _make_request
self._validate_conn(conn)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py”, line 850, in validate_conn
conn.connect()
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connection.py”, line 326, in connect
ssl_context=context)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/util/ssl
.py", line 329, in ssl_wrap_socket
return context.wrap_socket(sock, server_hostname=server_hostname)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py”, line 377, in wrap_socket
_context=self)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py”, line 752, in init
self.do_handshake()
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py”, line 988, in do_handshake
self._sslobj.do_handshake()
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ssl.py”, line 633, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:645)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/adapters.py”, line 440, in send
timeout=timeout
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/connectionpool.py”, line 639, in urlopen
_stacktrace=sys.exc_info()[2])
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/urllib3/util/retry.py”, line 388, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host=‘todoist.com’, port=443): Max retries exceeded with url: /API/v7/sync (Caused by SSLError(SSLError(1, ‘[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:645)’),))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/homeassistant/helpers/entity_component.py”, line 170, in _async_setup_platform
SLOW_SETUP_MAX_WAIT, loop=self.hass.loop)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/tasks.py”, line 392, in wait_for
return fut.result()
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/futures.py”, line 274, in result
raise self._exception
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/concurrent/futures/thread.py”, line 55, in run
result = self.fn(*self.args, **self.kwargs)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/homeassistant/components/calendar/todoist.py”, line 127, in setup_platform
api.sync()
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/todoist/api.py”, line 318, in sync
response = self._post(‘sync’, data=post_data)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/todoist/api.py”, line 291, in _post
response = self.session.post(url + call, **kwargs)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/sessions.py”, line 555, in post
return self.request(‘POST’, url, data=data, json=json, **kwargs)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/sessions.py”, line 508, in request
resp = self.send(prep, **send_kwargs)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/sessions.py”, line 618, in send
r = adapter.send(request, **kwargs)
File “/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/requests/adapters.py”, line 506, in send

@Jay2645 FYI It started working again on its own. Maybe Todoist was just temporarily down for a day or so. The refresh bug still exists but since I reboot HA practically every day because I’m always adding new stuff, it’s not really noticeable for me.

@Jay2645 I’m currently changing the Calendar system in HA, so that it can hold multiple events (enables the creation of an action calendar later on). (PR) I’m working on converting your Todoist integration now, I got some questions:

In line 330, you set the due_date to utcnow, does this not mean it’ll only take events before the current timestamp, instead of before the end of the day?

Since I don’t have a todoist premium account, just wanted to verify what the data structure looks like when all fields are filled, perhaps you have an example of this?

Might post some more questions when I’m going through it :slight_smile:

@tmatheussen That section of the code is for collecting everything due within a certain number of days (provided that number is defined).

For example, if we make a request at 2:01 PM to see everything due in the next 7 days, that’ll return everything due before 2:01 PM one week from now (including things like overdue events, which we should have done already).

In theory, this does mean if there’s an event at 2:02 PM, it doesn’t get included. In practice, I’m not sure it matters so much? It’s certainly a weird edge case, and perhaps it would be more intuitive to include everything due before the end of the day. It’s not something I’ve ever noticed, however.

I don’t have a Todoist premium account anymore, sadly. I believe everything is documented on their API page, however.

Well, I guess it works just fine in most cases (since currently the calendar only takes 1 event to show), but for example, if you would set 0 days (getting all today events, like the example docs say), you would only get previous events of today, no?