Close

Todolist Sync Monitor

A project log for Todolist Sync Monitor

Sync with [Todoist] website, Get the To do list and display on the Screen(ILI9488), You can update the task list when finish this task.

gavinchionggavinchiong 01/26/2023 at 07:040 Comments

The todolist website I often use is todoist.com, and I often use this website for task recording and application development planning. I want to develop an application based on RP2040 and WiFi module WizFi360, which can synchronize with the website and display the current task list on a screen.

“todoist” is the most popular app of organize work and life. The todoist app has more than 30 million users.

“todoist” provide Two open APIs for users to call their own data.

Rest API

The Todoist REST API offers the simplest approach to read and write data on the Todoist web service.

For most common application requirements this is our recommended API for external developers and uses an approach that should be familiar for anyone with experience calling RESTful APIs.

We've provided a Getting Started tutorial section within our REST API documentation to introduce you to the API and some common flows you'll encounter when developing Todoist applications. If you are totally new to our APIs or even unfamiliar with Todoist this is a great place to start.

Sync API

The Todoist Sync API is used by our web and mobile applications and is recommended for clients that will maintain a local representation of a user's full account data. It allows for incrementally sending and receiving account data by identifying positions in an account's state history using a sync token.

This API also contains additional endpoints for areas like account management and less common features that may not yet be implemented on the newer REST API.

Within the documentation for the Sync API you'll also find a Getting Started tutorial that will introduce you to using the Sync endpoint to send and receive data.

The detailed introduction of the api is in the following link:https://developer.todoist.com/guides/

Although the name of the project I developed is “Todolist Sync Monitor”, it uses the RSET api because it is more convenient.

I use wizfi360 as http client to send todolist RSET API request and get todolist parameters. WizFi360 is a WiFi module, which can connect to WiFi through commands and perform TCP or TCP (SSL) connections. I have used it many times and it is very convenient.

RP2040 acts as an MCU, after get todolist info from wizfi360, it performs data processing and displays the content on the screen.

This project is divided into five steps:

Step 1: Create New Account on the Todolist website and get API TOKEN;

Step2: Install library files and board support in the Arduino IDE;

Step 3: Get parameters from Todolist API through WizFi360;

Step 4: Displays the Todolist info on the screen(ILI9488);

Step 5: Finish the task on Todolist Sync Monitor.

The following are step by step instructions.

Step 1: Create New Account on the Todolist website and get API TOKEN;

After creat account in this website, you can see your API TOKEN on the "My accounts" page. Please record it, because this TOKEN will be required for future page visits.

in "Setting">>"Integrations">>"API token". you will get your "API token",

Your API token provides full access to view and modify your Todoist data. Please treat this like a password and take care when sharing it.

Step2: Install library files and board support in the Arduino IDE;

Add "WIZnet WizFi360-EVB-PICO" support to Arduino IDE

Open up the Arduino IDE and go to File->Preferences.

In the dialog that pops up, enter the following URL in the "Additional Boards Manager URLs" field:

https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

Search "WizFi360" and Install Board support by "Board Manager"

"Tools->Board:"***"-> Raspberry Pi RP2040 Boards(2.6.1) " Select “WIZnet WizFi360-EVB-PICO”.

Add “GFX Library for rduino”, this library support the screen ili9488.

Because we need to display the team's icon, we need to load a PNG library“PNGdec” to decode the image.

Step 3: Get parameters from Todolist API through WizFi360;

#include "WizFi360.h"// Wi-Fi info //char ssid[] = "WIZNET_test";       // your network SSID (name)//char pass[] = "********";          // your network password//int status = WL_IDLE_STATUS;       // the Wifi radio's status//// Initialize the Ethernet client objectWiFiClient client;

Initialize serial port for WizFi360 module and change the baudrate to 2000000bps(MAX baudrate for wizfi360).

The first initialization is 115200, and then setting the baud rate (2000000) is added to the initialization part of the WiZfi360 library, and the second time is changed to 2000000bps.

// initialize serial for WizFi360 module  Serial2.setFIFOSize(4096);  Serial2.begin(2000000);  WiFi.init(&Serial2);

Check the wizfi360 Link status of wifi in the “void setup()”

// check for the presence of the shield  if (WiFi.status() == WL_NO_SHIELD) {    while (true);// don't continue  }    // attempt to connect to WiFi network  while ( status != WL_CONNECTED) {    status = WiFi.begin(ssid, pass);// Connect to WPA/WPA2 network  }

“void loop()” There are 9 cases in The switch statement.

typedef enum {  send_project_request = 0,  get_project_list,  send_sections_request,  get_sections_list,  send_task_request,   get_task_list,  display_todolist,  todolist_close_task,   show_close_result, }STATE_;STATE_ currentState;

case send_project_request:       {        // if you get a connection, report back via serial        if (client.connectSSL(todoist_server,443)) {          delay(3000);          // Make a HTTP request                    client.println(String("GET /rest/v2/projects HTTP/1.1"));          client.println(String("Host:") + String(todoist_server));          client.println(String("Authorization: Bearer ") + String(todoist_token));                    client.println("Connection: close");          client.println();          json_String= "";          currentState = get_project_list;        }        else        {          client.stop();          delay(1000);        }       }      break;

Get all projects:

Get all projects:
$ curl -X GET \  https://api.todoist.com/rest/v2/projects \  -H "Authorization: Bearer $token"

Example response:

Example response:
[    {        "id": "220474322",        "name": "Inbox",        "comment_count": 10,        "order": 0,        "color": "grey",        "is_shared": false,        "is_favorite": false,        "is_inbox_project": true,        "is_team_inbox": false,        "view_style": "list",        "url": "https://todoist.com/showProject?id=220474322",        "parent_id": null,    }]
case get_project_list:       {                    while (client.available()) {            json_String += (char)client.read();            data_now =1;           }                    if(data_now)          {#ifdef debug_msg            Serial.println(json_String); #endif              for(uint8_t i=0; i<todoist_project_max; i++)            {              todoist_project[i].Exist = true;              dataStart = json_String.indexOf("\"id\": \"",dataEnd) + strlen("\"id\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_project[i].ID = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_project.ID:");              Serial.println(todoist_project[i].ID);#endif                dataStart = json_String.indexOf("\"color\": \"", dataEnd) + strlen("\"color\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_project[i].Color_Str = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_project.Color_Str:");              Serial.println(todoist_project[i].Color_Str);#endif                dataStart = json_String.indexOf("\"name\": \"", dataEnd) + strlen("\"name\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_project[i].Name = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_project.Name:");              Serial.println(todoist_project[i].Name);#endif                dataStart = json_String.indexOf("\"url\": \"", dataEnd) + strlen("\"url\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_project[i].Url = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_project.Url:");              Serial.println(todoist_project[i].Url);#endif                    if((dataEnd+40)> json_String.length())              {                todoist_project_num = i+1;                data_now =0;                 client.stop();                delay(3000);                currentState = send_sections_request;                return;              }            }           }       }      break;

The obtained PROJECT information includes project ID, name, and color etc.

struct _todoist_project{boolean Exist = false;String ID;String Name;String Color_Str;} ;_todoist_project todoist_project[todoist_project_max];uint8_t todoist_project_num = 0;uint8_t todoist_project_cnt = 0;

The obtained color information is a text description, which needs to be converted to RGB565 color through the following function.

uint32_t Str2RGB565(String str){  uint32_t RGB565;  if(str == "berry_red")  {RGB565 = 0x8800;}  else if(str == "red")   {RGB565 = 0xF800;}  else if(str == "orange"){RGB565 = 0xFD20;}  else if(str == "yellow"){RGB565 = 0xFFE0;}  else if(str == "olive_green") {RGB565 = 0x8400;}  else if(str == "lime_green")  {RGB565 = 0x07E0;}  else if(str == "green") {RGB565 = 0x0400;}  else if(str == "mint_green")  {RGB565 = 0x3666;}  else if(str == "teal")  {RGB565 = 0x0410;}  else if(str == "sky_blue")  {RGB565 = 0x867D;}  else if(str == "light_blue"){RGB565 = 0xAEDC;}  else if(str == "blue")    {RGB565 = 0x001F;}  else if(str == "grape")   {RGB565 = 0x8010;}  else if(str == "violet")  {RGB565 = 0x881F;}  else if(str == "lavender"){RGB565 = 0xE73F;}  else if(str == "magenta") {RGB565 = 0xF81F;}  else if(str == "salmon")  {RGB565 = 0xFC0E;}  else if(str == "charcoal"){RGB565 = 0xD69A;}  else if(str == "grey")  {RGB565 = 0x8410;}  else if(str == "taupe") {RGB565 = 0xBC71;}  else {RGB565 = 0xFFFF;}    return RGB565;}

This is the color chart:

case send_sections_request:       {          if (client.connectSSL(todoist_server,443)) {          delay(3000);          // Make a HTTP request                    client.println(String("GET /rest/v2/sections HTTP/1.1"));          client.println(String("Host: ") + todoist_server);          client.println(String("Authorization: Bearer ") + String(todoist_token));                    client.println("Connection: close");          client.println();          json_String= "";          currentState = get_sections_list;                 }          else          {            client.stop();            delay(1000);          }       }      break;

Get all sections:

Get all sections:
$ curl -s -H "Authorization: Bearer $token" \    https://api.todoist.com/rest/v2/sections

Example response:

Example response:
[    {        "id": "7025",        "project_id": "2203306141",        "order": 1,        "name": "Groceries"    }]

case get_sections_list:       {          while (client.available()) {            json_String += (char)client.read();            data_now = 1;             dataEnd = 0;          }                    if(data_now)          {#ifdef debug_msg            Serial.println(json_String); #endif            for(uint8_t m=0; m<todoist_sections_max; m++)            {              todoist_sections[m].Exist = true;              dataStart = json_String.indexOf("\"id\": \"",dataEnd) + strlen("\"id\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_sections[m].ID = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_sections.ID:");              Serial.println(todoist_sections[m].ID);#endif              dataStart = json_String.indexOf("\"project_id\": \"",dataEnd) + strlen("\"project_id\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_sections[m].Project = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_sections.Project:");              Serial.println(todoist_sections[m].Project);#endif              dataStart = json_String.indexOf("\"name\": \"",dataEnd) + strlen("\"name\": \"");              dataEnd = json_String.indexOf("\"", dataStart);               todoist_sections[m].Name = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_sections.Name:");              Serial.println(todoist_sections[m].Name);#endif                 if((dataEnd+40)> json_String.length())              {                todoist_sections_num =m+1;                data_now =0;                 client.stop();                delay(3000);                currentState = send_task_request;                return;              }            }         }       }      break;

The obtained SECTION information includes Section ID, name, and project etc.

struct _todoist_sections{  boolean Exist = false;  String ID;  String Name;  String Project;};_todoist_sections todoist_sections[todoist_sections_max];uint16_t todoist_sections_num;uint16_t todoist_sections_cnt = 0;

case send_task_request:      {        if (client.connectSSL(todoist_server,443)) {        delay(3000);        // Make a HTTP request                  client.println(String("GET /rest/v2/tasks HTTP/1.1"));        client.println(String("Host: ") + todoist_server);        client.println(String("Authorization: Bearer ") + String(todoist_token));                  client.println("Connection: close");        client.println();        json_String= "";        currentState = get_task_list;               }        else        {          client.stop();          delay(1000);        }      }      break;

Get active tasks:

Get active tasks:
$ curl -X GET \  https://api.todoist.com/rest/v2/tasks \  -H "Authorization: Bearer $token"

Example response:

Example response:
[    {        "creator_id": "2671355",        "created_at": "2019-12-11T22:36:50.000000Z",        "assignee_id": "2671362",        "assigner_id": "2671355",        "comment_count": 10,        "is_completed": false,        "content": "Buy Milk",        "description": "",        "due": {            "date": "2016-09-01",            "is_recurring": false,            "datetime": "2016-09-01T12:00:00.000000Z",            "string": "tomorrow at 12",            "timezone": "Europe/Moscow"        },        "id": "2995104339",        "labels": ["Food", "Shopping"],        "order": 1,        "priority": 1,        "project_id": "2203306141",        "section_id": "7025",        "parent_id": "2995104589",        "url": "https://todoist.com/showTask?id=2995104339"    },    ...]

case get_task_list:      {         while (client.available()) {            json_String += (char)client.read();            data_now = 1;             dataEnd = 0;          }                    if(data_now)          {            dataStart = json_String.indexOf("Content-Length: ") + strlen("Content-Length: ");            dataEnd = json_String.indexOf("\r\n", dataStart);             dataStr = json_String.substring(dataStart, dataEnd);            dataStart = json_String.indexOf("\r\n\r\n", dataEnd);             todoist_task_len = dataStr.toInt()-(json_String.length() - dataStart - 4);//HexStr2Int(dataStr)-(json_String.length() - dataEnd - 4);#ifdef debug_msg            Serial.print("todoist_task_len:");            Serial.println(todoist_task_len);#endif              while(todoist_task_len)            {               while (client.available()) {                json_String += (char)client.read();                todoist_task_len --;              }            }#ifdef debug_msg            Serial.println(json_String); #endif            for(uint8_t n=0; n<todoist_task_max; n++)            {              todoist_task[n].Exist = true;              dataStart = json_String.indexOf("\"id\": \"",dataEnd) + strlen("\"id\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_task[n].ID = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_task.ID:");              Serial.println(todoist_task[n].ID);#endif                dataStart = json_String.indexOf("\"section_id\": ",dataEnd) + strlen("\"section_id\": ")+1;              dataEnd = json_String.indexOf(",", dataStart)-1;               todoist_task[n].Section = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_task.section_id:");              Serial.println(todoist_task[n].Section);#endif                dataStart = json_String.indexOf("\"parent_id\": ",dataEnd) + strlen("\"parent_id\": ")+1;              dataEnd = json_String.indexOf(",", dataStart)-1;               todoist_task[n].Parent = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_task.Parent_id:");              Serial.println(todoist_task[n].Parent);#endif                dataStart = json_String.indexOf("\"content\": \"",dataEnd) + strlen("\"content\": \"");              dataEnd = json_String.indexOf("\",", dataStart);               todoist_task[n].Name = json_String.substring(dataStart, dataEnd);#ifdef debug_msg               Serial.print("todoist_task.Name:");              Serial.println(todoist_task[n].Name);#endif                dataStart = json_String.indexOf("\"is_completed\": ",dataEnd) + strlen("\"is_completed\": ");              dataEnd = json_String.indexOf(",", dataStart);               dataStr = json_String.substring(dataStart, dataEnd);#ifdef debug_msg              Serial.print("todoist_task.Finished:");              Serial.println(dataStr);#endif                if(dataStr == "true"){todoist_task[n].Finished = true;}              dataEnd = json_String.indexOf("\"url\": ",dataEnd) + strlen("\"url\": ");                            if((dataEnd+60)> json_String.length())              {                todoist_task_num =n+1;                data_now =0;                 client.stop();                currentState = display_todolist;                return;              }            }         }      }      break;

The obtained TASK information includes task ID, name, Section and Parent etc.

struct _todoist_task{  boolean Exist = false;  boolean Finished;  String ID;  String Section;  String Parent;  String Name;};_todoist_task todoist_task[todoist_task_max];

So far, we have obtained project, section and task information respectively. The next step is to handle the display task.

Step 4: Displays the Todolist info on the screen(ILI9488);

#include <Arduino_GFX_Library.h>Arduino_GFX *tft = create_default_Arduino_GFX();

define of pin which is used by ILI9488 in the "libraries\GFX_Library_for_Arduino\src\Arduino_GFX_Library.h"

#elif defined(ARDUINO_RASPBERRY_PI_PICO)||defined(ARDUINO_WIZNET_WIZFI360_EVB_PICO)||defined(ARDUINO_WIZNET_5100S_EVB_PICO)#define DF_GFX_SCK 26#define DF_GFX_MOSI 27#define DF_GFX_MISO GFX_NOT_DEFINED#define DF_GFX_CS 25#define DF_GFX_DC 23#define DF_GFX_RST 28#define DF_GFX_BL 22

define library used by ILI9488 in the "libraries\GFX_Library_for_Arduino\src\Arduino_GFX_Library.cpp"

#include "Arduino_GFX_Library.h"#define lCD_ILI9488Arduino_DataBus *create_default_Arduino_DataBus(){#if defined(ARDUINO_ARCH_NRF52840)    return new Arduino_NRFXSPI(DF_GFX_DC, DF_GFX_CS, DF_GFX_SCK, DF_GFX_MOSI, DF_GFX_MISO);//#elif defined(ARDUINO_RASPBERRY_PI_PICO) || defined(ARDUINO_RASPBERRY_PI_PICO_W)//    return new Arduino_RPiPicoSPI(DF_GFX_DC, DF_GFX_CS, DF_GFX_SCK, DF_GFX_MOSI, DF_GFX_MISO, spi0);#elif defined(ARDUINO_RASPBERRY_PI_PICO)|| defined(ARDUINO_RASPBERRY_PI_PICO_W) ||defined(ARDUINO_WIZNET_WIZFI360_EVB_PICO)||defined(ARDUINO_WIZNET_5100S_EVB_PICO)    return new Arduino_RPiPicoSPI(DF_GFX_DC, DF_GFX_CS, DF_GFX_SCK, DF_GFX_MOSI, DF_GFX_MISO, spi1);#elif defined(ESP32)    return new Arduino_ESP32SPI(DF_GFX_DC, DF_GFX_CS, DF_GFX_SCK, DF_GFX_MOSI, DF_GFX_MISO);#elif defined(ESP8266)    return new Arduino_ESP8266SPI(DF_GFX_DC, DF_GFX_CS);#else    return new Arduino_HWSPI(DF_GFX_DC, DF_GFX_CS);#endif}Arduino_GFX *create_default_Arduino_GFX(){    Arduino_DataBus *bus = create_default_Arduino_DataBus();#if defined(WIO_TERMINAL)    return new Arduino_ILI9341(bus, DF_GFX_RST, 1 /* rotation */);#elif defined(ARDUINO_RASPBERRY_PI_PICO)|| defined(ARDUINO_RASPBERRY_PI_PICO_W) ||defined(ARDUINO_WIZNET_WIZFI360_EVB_PICO)||defined(ARDUINO_WIZNET_5100S_EVB_PICO)    {         #if defined (lCD_ILI9488)        {            return new Arduino_ILI9488_18bit(bus, DF_GFX_RST, 1 /* rotation */, false /* IPS */);        }        #else        {            return new Arduino_GC9A01(bus, DF_GFX_RST, 0 /* rotation */, true /* IPS */);        }        #endif            }#elif defined(ESP32_S3_BOX)    return new Arduino_ILI9342(bus, DF_GFX_RST, 0 /* rotation */);#elif defined(M5STACK_CORE)    return new Arduino_ILI9342(bus, DF_GFX_RST, 2 /* rotation */);#elif defined(ODROID_GO)    return new Arduino_ILI9341(bus, DF_GFX_RST, 3 /* rotation */);#elif defined(TTGO_T_WATCH)    return new Arduino_ST7789(bus, DF_GFX_RST, 0 /* rotation */, true /* IPS */, 240, 240, 0, 80);#else    return new Arduino_ILI9341(bus, DF_GFX_RST, 0 /* rotation */);#endif}

Initialize the screen and open the backlight of the screen in the “void setup()”

tft->begin();  tft->fillScreen(WHITE);  pinMode(22, OUTPUT);   digitalWrite(22, HIGH);

void display_logo(uint16_t x,uint16_t y,uint16_t color){  uint8_t cloud_pixel[5*11]=  {    0b00111110,0b01000001,0b01000001,0b01000001,0b00100010, // C    0b00000000,0b00000000,0b01111111,0b00000000,0b00000000, // l    0b00001110,0b00010001,0b00010001,0b00010001,0b00001110, // o    0b00011110,0b00000001,0b00000001,0b00000001,0b00011111, // u    0b00001110,0b00010001,0b00010001,0b00010001,0b01111111, // d    0b00000000,0b00000000,0b00000000,0b00000000,0b00000000, // space    0b01111111,0b01001000,0b01001000,0b01001000,0b00110000, // P    0b00000000,0b00000000,0b01011111,0b00000000,0b00000000, // i    0b00010001,0b00001010,0b00000100,0b00001010,0b00010001, // x    0b00001110,0b00010101,0b00010101,0b00010101,0b00001100, // e    0b00000000,0b00000000,0b01111111,0b00000000,0b00000000  // l  };  uint16_t _x = x - (5*5*5) - 6;  uint16_t _y = y - 20;  for(uint8_t i=0;i<11;i++)  {        if(i == 1 || i == 2 || i ==5 || i==6 ||i==7 ||i==8 || i == 10)    {       _x = _x -6;    }    else    {       _x = _x+4;    }       for(uint8_t m=0;m<5;m++)    {      _x = _x +5;      _y = y - 20;      for(uint8_t n=0;n<8;n++)      {        if((cloud_pixel[i*5+m]>>(7-n))&0x01)        {          tft->fillRect(_x+1,_y+1,4,4,color);        }        _y += 5;      }    }      }}

void display_todoist_icon(uint8_t PNGnum){  char szTemp[256];  int rc;  if(PNGnum == 1 )  {    rc = png.openFLASH((uint8_t *)TodoistIcon, sizeof(TodoistIcon), PNGDraw);  }  else if(PNGnum == 2)  {    rc = png.openFLASH((uint8_t *)TodoistIcon_mini, sizeof(TodoistIcon_mini), PNGDraw);  }  if (rc == PNG_SUCCESS) {    rc = png.decode(NULL, 0); // no private structure and skip CRC checking    png.close();  }}

when not connect to the WiFi network.

when connected to the WiFi network.

void display_wifi_status(uint8_t x,uint8_t y){  if( status != WL_CONNECTED)  {    tft->fillCircle(x,y,3,DARKGREY);    tft->fillArc(x,y, 5, 7, 225, 315, DARKGREY);     tft->fillArc(x,y, 9, 11, 225, 315, DARKGREY);     tft->fillArc(x,y, 13, 15, 225, 315, DARKGREY);   }  else  {    tft->fillCircle(x,y,3,GREEN);    tft->fillArc(x,y, 5, 7, 225, 315, GREEN);     tft->fillArc(x,y, 9, 11, 225, 315, GREEN);     tft->fillArc(x,y, 13, 15, 225, 315, GREEN);   }}

void display_dashboard(){           tft->fillScreen(WHITE);    image_x = 6;    image_y = 0;    display_todoist_icon(2);        tft->setTextColor(DARKGREEN);    tft->setTextSize(2);    tft->setCursor(150, 1);    tft->print(String("Todolist Sync Monitor"));    tft->drawLine(20,18,480,18,RED);}

case display_todolist:     {          uint8_t display_row_num = 0;        uint8_t display_page_num = 0;        uint8_t display_string_len = 0;        String display_string;        display_task_num = 0;        display_dashboard();         for(uint8_t i=0; i<todoist_project_num; i++)        {           if(jump_out_flag)           {            jump_out_flag =0;            return;           }#ifdef debug_msg          Serial.print("todoist_project_num:");          Serial.println(i);#endif            if(todoist_project[i].Exist)          {            tft->fillCircle(12,26+(20*display_row_num),5,Str2RGB565(todoist_project[i].Color_Str));            tft->setTextColor(Str2RGB565(todoist_project[i].Color_Str));            tft->setTextSize(2);                        display_string_len = todoist_project[i].Name.length();            if(display_string_len > 38)            {              while(display_string_len > 38)              {                display_string = todoist_project[i].Name.substring(todoist_project[i].Name.length()- display_string_len, todoist_project[i].Name.length()-display_string_len+38);                display_string_len -= 38;                tft->setCursor(24, 20+(20*display_row_num));                tft->print(String("[") + display_string + String("]"));                display_row_num++;              }              display_string = todoist_project[i].Name.substring(todoist_project[i].Name.length()-display_string_len, todoist_project[i].Name.length());              tft->setCursor(24, 20+(20*display_row_num));              tft->print(String("[") + display_string + String("]"));              display_row_num++;            }            else            {              tft->setCursor(24, 20+(20*display_row_num));              tft->print(String("[") + todoist_project[i].Name + String("]"));              display_row_num++;            }                        for(uint8_t m=0; m<todoist_sections_num; m++)            {              if(jump_out_flag)              {                jump_out_flag =0;                return;              }              if(todoist_sections[m].Exist)              {                if(todoist_sections[m].Project == todoist_project[i].ID)                {#ifdef debug_msg                  Serial.print("todoist_sections_num:");                  Serial.println(m);#endif                    display_string_len = todoist_sections[m].Name.length();                  if(display_string_len > 38)                  {                    while(display_string_len > 38)                    {                      display_string = todoist_sections[m].Name.substring(todoist_sections[m].Name.length()- display_string_len, todoist_sections[m].Name.length()-display_string_len+38);                      display_string_len -= 38;                      tft->setCursor(24, 20+(20*display_row_num));                        tft->print(display_string);                      display_row_num++;                      if(display_row_num == 15)                      {                        display_row_num =0;                         wait_task_finish_click();display_dashboard();                      }                    }                    display_string = todoist_sections[m].Name.substring(todoist_sections[m].Name.length()- display_string_len, todoist_sections[m].Name.length());                    tft->setCursor(24, 20+(20*display_row_num));                      tft->print(display_string);                    display_row_num++;                    if(display_row_num == 15)                    {                      display_row_num =0;                       wait_task_finish_click();display_dashboard();                    }                  }                  else                  {                    tft->setCursor(24, 20+(20*display_row_num));                      tft->print(todoist_sections[m].Name);                     display_row_num++;                        if(display_row_num == 15)                    {                      display_row_num =0;                       wait_task_finish_click();display_dashboard();                    }                  }                  tft->setTextColor(DARKGREY);                  for(uint8_t n=0; n<todoist_task_num; n++)                  {                     if(jump_out_flag)                     {                      jump_out_flag =0;                      return;                     }                    if(todoist_task[n].Exist)                    {                      if((todoist_task[n].Section == todoist_sections[m].ID)&& (todoist_task[n].Parent == "ul"))                      {#ifdef debug_msg//                        Serial.print("todoist_task_num:");//                        Serial.println(n);//                        Serial.print("todoist_task_ID:");//                        Serial.println(todoist_task[n].ID);//                        Serial.print("todoist_task_Name:");//                        Serial.println(todoist_task[n].Name);#endif                          tft->drawCircle(36,26+(20*display_row_num),5,Str2RGB565(todoist_project[i].Color_Str));                        todoist_task_select[display_task_num].x = 36;                        todoist_task_select[display_task_num].y = 26+(20*display_row_num);                        todoist_task_select[display_task_num].ID = todoist_task[n].ID;                        todoist_task_select[display_task_num].Name = todoist_task[n].Name;                        display_task_num++;                        if(todoist_task[n].Finished)                        {                          tft->fillCircle(36,26+(20*display_row_num),3,Str2RGB565(todoist_project[i].Color_Str));                            tft->drawLine(48,26+(20*display_row_num),48+todoist_task[n].Name.length()*12,26+(20*display_row_num),Str2RGB565(todoist_project[i].Color_Str));                        }                        display_string_len = todoist_task[n].Name.length();                        if(display_string_len > 36)                        {                          while(display_string_len > 36)                          {                            display_string = todoist_task[n].Name.substring(todoist_task[n].Name.length()- display_string_len, todoist_task[n].Name.length()-display_string_len+36);                            display_string_len -= 36;                            tft->setCursor(48, 20+(20*display_row_num));                            tft->print(display_string);                            display_row_num++;                            if(display_row_num == 15)                            {                              display_row_num =0;                               wait_task_finish_click();display_dashboard();                            }                          }                          display_string = todoist_task[n].Name.substring(todoist_task[n].Name.length()-display_string_len, todoist_task[n].Name.length());                          tft->setCursor(48, 20+(20*display_row_num));                            tft->print(display_string);                          display_row_num++;                          if(display_row_num == 15)                          {                            display_row_num =0;                             wait_task_finish_click();display_dashboard();                          }                        }                        else                        {                           tft->setCursor(48, 20+(20*display_row_num));                           tft->print(todoist_task[n].Name);                           display_row_num++;                           if(display_row_num == 15)                           {                            display_row_num =0;                             wait_task_finish_click();display_dashboard();                           }                        }                      for(uint8_t p=n; p<todoist_task_num; p++)                      {                         if(jump_out_flag)                         {                          jump_out_flag =0;                          return;                         }                        if((todoist_task[p].Parent == todoist_task[n].ID))                        {#ifdef debug_msg//                          Serial.print("todoist_task_num:");//                          Serial.println(p);//                          Serial.print("todoist_task_ID:");//                          Serial.println(todoist_task[p].ID);//                          Serial.print("todoist_task_Parent:");//                          Serial.println(todoist_task[p].Parent);//                          Serial.print("todoist_task.ID");//                          Serial.print(n);//                          Serial.print(":");//                          Serial.println(todoist_task[n].ID);#endif                            tft->drawCircle(60,26+(20*display_row_num),5,Str2RGB565(todoist_project[i].Color_Str));                          todoist_task_select[display_task_num].x = 60;                          todoist_task_select[display_task_num].y = 26+(20*display_row_num);                          todoist_task_select[display_task_num].ID = todoist_task[p].ID;                          todoist_task_select[display_task_num].Name = todoist_task[p].Name;                           display_task_num++;                                                if(todoist_task[p].Finished)                          {                            tft->fillCircle(60,26+(20*display_row_num),3,Str2RGB565(todoist_project[i].Color_Str));                             tft->drawLine(72,26+(20*display_row_num),72+todoist_task[p].Name.length()*12,26+(20*display_row_num),Str2RGB565(todoist_project[i].Color_Str));                                                   }                          display_string_len = todoist_task[p].Name.length();                          if(display_string_len > 34)                          {                            while(display_string_len > 34)                            {                              display_string = todoist_task[p].Name.substring(todoist_task[p].Name.length()- display_string_len, todoist_task[p].Name.length()-display_string_len+34);                              display_string_len -= 34;                              tft->setCursor(72, 20+(20*display_row_num));                              tft->print(display_string);                              display_row_num++;                              if(display_row_num == 15)                              {                                display_row_num =0;                                   wait_task_finish_click();display_dashboard();                              }                            }                            display_string = todoist_task[p].Name.substring(todoist_task[p].Name.length()-display_string_len, todoist_task[p].Name.length());                            tft->setCursor(72, 20+(20*display_row_num));                              tft->print(display_string);                            display_row_num++;                          }                          else                          {                             tft->setCursor(72, 20+(20*display_row_num));                             tft->print(todoist_task[p].Name);                             display_row_num++;                          }                          if(display_row_num == 15)                          {                            display_row_num =0;                                    wait_task_finish_click();display_dashboard();                          }                          for(uint8_t q=0; q<todoist_task_num; q++)                          {                             if(jump_out_flag)                             {                              jump_out_flag =0;                              return;                             }                            if((todoist_task[q].Parent == todoist_task[p].ID))                            {#ifdef debug_msg//                          Serial.print("todoist_task_num2:");//                          Serial.println(q);//                          Serial.print("todoist_task_ID2:");//                          Serial.println(todoist_task[q].ID);//                          Serial.print("todoist_task_Parent2:");//                          Serial.println(todoist_task[q].Parent);#endif                                tft->drawCircle(84,26+(20*display_row_num),5,Str2RGB565(todoist_project[i].Color_Str));                              todoist_task_select[display_task_num].x = 84;                              todoist_task_select[display_task_num].y = 26+(20*display_row_num);                              todoist_task_select[display_task_num].ID = todoist_task[q].ID;                              todoist_task_select[display_task_num].Name = todoist_task[q].Name;                                display_task_num++;                                                    if(todoist_task[q].Finished)                              {                                tft->fillCircle(84,26+(20*display_row_num),3,Str2RGB565(todoist_project[i].Color_Str));                                 tft->drawLine(96,26+(20*display_row_num),96+todoist_task[q].Name.length()*12,26+(20*display_row_num),Str2RGB565(todoist_project[i].Color_Str));                                                       }                              display_string_len = todoist_task[q].Name.length();                              if(display_string_len > 32)                              {                                while(display_string_len > 32)                                {                                  display_string = todoist_task[q].Name.substring(todoist_task[q].Name.length()- display_string_len, todoist_task[q].Name.length()-display_string_len+32);                                  display_string_len -= 32;                                  tft->setCursor(96, 20+(20*display_row_num));                                  tft->print(display_string);                                  display_row_num++;                                  if(display_row_num == 15)                                  {                                    display_row_num =0;                                            wait_task_finish_click();display_dashboard();                                   }                                }                                display_string = todoist_task[q].Name.substring(todoist_task[q].Name.length()-display_string_len, todoist_task[q].Name.length());                                tft->setCursor(96, 20+(20*display_row_num));                                  tft->print(display_string);                                display_row_num++;                              }                              else                              {                                 tft->setCursor(96, 20+(20*display_row_num));                                 tft->print(todoist_task[q].Name);                                 display_row_num++;                              }                              if(display_row_num == 15)                              {                                display_row_num =0;                                wait_task_finish_click();display_dashboard();                              }                            }                          }                        }                      }                                           }                    }                  }                }              }            }            tft->setTextColor(DARKGREY);            for(uint8_t n=0; n<todoist_task_num;n++)            {               if(jump_out_flag)               {                jump_out_flag =0;                return;               }              if(todoist_task[n].Parent == todoist_project[i].ID)                  {#ifdef debug_msg                 Serial.print("todoist_task_num:");                 Serial.println(n);#endif                tft->drawCircle(48,26+(20*display_row_num),5,Str2RGB565(todoist_project[i].Color_Str));                todoist_task_select[display_task_num].x = 48;                todoist_task_select[display_task_num].y = 26+(20*display_row_num);                todoist_task_select[display_task_num].ID = todoist_task[n].ID;                todoist_task_select[display_task_num].Name = todoist_task[n].Name;                display_task_num++;                display_string_len = todoist_task[n].Name.length();                if(display_string_len > 34)                {                  while(display_string_len > 34)                  {                    display_string = todoist_task[n].Name.substring(todoist_task[n].Name.length()- display_string_len, todoist_task[n].Name.length()-display_string_len+34);                    display_string_len -= 34;                    tft->setCursor(24, 20+(20*display_row_num));                    tft->print(display_string);                    display_row_num++;                    if(display_row_num == 15)                    {                      display_row_num =0;                              wait_task_finish_click();display_dashboard();                    }                  }                  display_string = todoist_task[n].Name.substring(todoist_task[n].Name.length()-display_string_len, todoist_task[n].Name.length());                  tft->setCursor(24, 20+(20*display_row_num));                  tft->print(display_string);                  display_row_num++;                }                else                {                  tft->setCursor(48, 20+(20*display_row_num));                  tft->print(todoist_task[n].Name);                   display_row_num++;                }                if(display_row_num == 15)                {                   display_row_num =0;                   wait_task_finish_click();                   display_dashboard();                          }              }            }          }          else          {            return;          }                }        wait_task_finish_click();        if(display_time_cnt == 6)        {          display_time_cnt = 0;          display_dashboard();          tft->setTextColor(DARKGREEN);          tft->setTextSize(2);          tft->setCursor(132, 120);          tft->print("Get Todolist Again");          currentState = send_project_request;        }        else        {          display_time_cnt++;        }        //currentState = todolist_close_task;     }    break;

The interface for displaying tasks may require multiple interfaces to be displayed alternately, and each interface stays for 10 seconds. When the button is pressed, it can switch between tasks and reset the timer; and the last task beyond this interface will be automatically refreshed as the next task interface.The interface for displaying tasks may require multiple interfaces to be displayed alternately, and each interface stays for 10 seconds. When the button is pressed, it can switch between tasks and reset the timer; and the last task beyond this interface will be automatically refreshed as the next task interface. After displaying all the interfaces 6 times in a loop, jump to case "send_project_request" to get task list information from the server again.

This is the processing flow of the button. Pressing the button can switch between tasks.

void wait_task_finish_click(){   uint8_t task_select = 0;      for(uint16_t time_cnt; time_cnt <10000; time_cnt++)   {      if(buttonState)      {        time_cnt = 0;        delay(150);        if(buttonState)        {                   for(uint16_t time_cnt2 = 0; time_cnt2 <1000; time_cnt2++)           {              if(!buttonState)              {                 if(task_select > 0 && task_select < display_task_num)                 {                    tft->fillCircle(todoist_task_select[task_select-1].x,todoist_task_select[task_select-1].y,3,WHITE);                    tft->fillCircle(todoist_task_select[task_select].x,todoist_task_select[task_select].y,3,BLACK);                 }                 else if(task_select < display_task_num)                 {                    tft->fillCircle(todoist_task_select[task_select].x,todoist_task_select[task_select].y,3,BLACK);                 }                 else if(task_select == display_task_num)                 {                    task_select = 0;                    display_task_num=0;                    return;                 }                task_select++;                time_cnt2 = 1000;              }              else              {                delay(1);              }           }           if(buttonState)           {              tft->drawLine(todoist_task_select[task_select-1].x+12,todoist_task_select[task_select-1].y,todoist_task_select[task_select-1].x+todoist_task_select[task_select-1].Name.length()*12,todoist_task_select[task_select-1].y,BLACK);              delay(500);                            todoist_task_close.ID = todoist_task_select[task_select-1].ID;                todoist_task_close.Name = todoist_task_select[task_select-1].Name;                            currentState = todolist_close_task;              jump_out_flag = 1;              return;           }                   }      }      else      {        delay(1);      }       }   display_task_num=0;   }

Step 5: Finish the task on Todolist Sync Monitor.

When the task is completed, the task can be closed by long-press the button.

case todolist_close_task:     {      if(!display_close_int)      {        //currentState = send_project_request;        display_dashboard();        tft->drawRect(50,70,385,150,BLACK);        tft->setTextColor(BLACK);        tft->setTextSize(2);        tft->setCursor(60, 80);        tft->print(String("Do you want to close this task:"));        tft->setTextColor(DARKGREY);        tft->setTextSize(2);        tft->setCursor((480-(12*todoist_task_close.Name.length()))/2, 120);        tft->print(todoist_task_close.Name);                tft->fillRect(160,175,46,25,WHITE);        tft->drawRect(160,175,46,25,RED);        tft->setTextColor(RED);        tft->setTextSize(2);        tft->setCursor(165, 180);        tft->print("YES");        tft->fillRect(280,175,40,25,WHITE);        tft->drawRect(280,175,40,25,GREEN);        tft->setTextColor(GREEN);        tft->setTextSize(2);        tft->setCursor(288, 180);        tft->print("NO");                display_close_int = true;          }      else      {        if(buttonState)        {          delay(150);          if(buttonState)          {            yes_or_no =!yes_or_no;            for(uint16_t time_cnt = 0; time_cnt <1000; time_cnt++)            {              if(!buttonState)              {                if(yes_or_no)                {                  tft->fillRect(160,175,46,25,WHITE);                  tft->drawRect(160,175,46,25,RED);                  tft->setTextColor(RED);                  tft->setTextSize(2);                  tft->setCursor(165, 180);                  tft->print("YES");                          tft->fillRect(280,175,40,25,GREEN);                  tft->setTextColor(WHITE);                  tft->setTextSize(2);                  tft->setCursor(288, 180);                  tft->print("NO");                }                else                {                  tft->fillRect(160,175,46,25,RED);                  tft->setTextColor(WHITE);                  tft->setTextSize(2);                  tft->setCursor(165, 180);                  tft->print("YES");                                    tft->fillRect(280,175,40,25,WHITE);                  tft->drawRect(280,175,40,25,GREEN);                  tft->setTextColor(GREEN);                  tft->setTextSize(2);                  tft->setCursor(288, 180);                  tft->print("NO");                }                                time_cnt = 1000;                      }              else              {                delay(1);                              }              if(time_cnt == 999)              {                display_dashboard();                                if(yes_or_no)                {                  tft->setTextColor(RED);                  tft->setTextSize(2);                  tft->setCursor(180, 120);                  tft->print("Task Closing");                                    yes_or_no = false;                  if (client.connectSSL(todoist_server,443)) {                  delay(3000);                  // Make a HTTP request                            client.println(String("POST /rest/v2/tasks/")+todoist_task_close.ID+ String("/close HTTP/1.1"));                  client.println(String("Host: ") + todoist_server);                  client.println(String("Authorization: Bearer ") + String(todoist_token));                            client.println("Connection: close");                  client.println();                  json_String= "";                                 currentState = show_close_result;                  }                }                else                {                                    currentState = display_todolist;                }                display_close_int = false;              }            }          }        }              }     }    break;        case show_close_result:    {       while (client.available()) {        json_String += (char)client.read();        data_now = 1;         dataEnd = 0;      }                if(data_now)      {#ifdef debug_msg         Serial.println(json_String);    #endif          client.stop();          delay(2000);         currentState = send_project_request;      }    }    break;  }}

Take closing this task"Todoist API Communication" as an example, the interface is displayed as follows.

Close a task:

Close a task:
$ curl -X POST "https://api.todoist.com/rest/v2/tasks/2995104339/close" \    -H "Authorization: Bearer $token"

The API returns an empty re

Discussions