Pular para o conteúdo
Aprenda

Como usar o Google Calendar no ESP

Julia P. Metzger 9 min de leitura

Artigo em construção.

Olá meus amigos, enquanto o artigo está em construção, vou deixar os códigos para quem quiser ir usando. Logo logo está pagina vai conter todo o passo a passo que você acessou em vídeo, mas se quiser ir dar uma olhadinha nos outros artigos e tutoriais aqui do site, tenho CERTEZA que você vai gostar desse espaço que estou construindo para nós por aqui!

Código Arduino

 

// Código desenvolvido por Julialabs FIT IA
// ESP8266 + SSD1306 128×64
//
// BOTÃO 1:
// GPIO0 -> GND
// Seleciona a próxima tarefa
//
// BOTÃO 2:
// GPIO2 -> GND
// Conclui a tarefa selecionada
// ============================================================

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// CONFIGURAÇÕES WIFI e GOOGLE
const char* WIFI_SSID = “SSD”;
const char* WIFI_PASS = “SENHA”;

const char* SCRIPT_URL =
“SUA_CHAVE_URL?key=1234”;

// OLED DISPLAY SSD1306

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

Adafruit_SSD1306 display(
SCREEN_WIDTH,
SCREEN_HEIGHT,
&Wire,
OLED_RESET);
// BOTÕES
#define BOTAO_SELECIONAR 0
#define BOTAO_CONCLUIR 2

// TEMPOS

const unsigned long INTERVALO_ATUALIZACAO = 1UL * 60UL * 1000UL; //atualiza a cada 1min

const unsigned long DEBOUNCE = 200;

// ITENS DA LISTA DE TAREFA OU EVENTOS

const int MAX_ITENS = 20;

String itemsTitle[MAX_ITENS];
String itemsTime[MAX_ITENS];
String itemsType[MAX_ITENS];

String itemsListId[MAX_ITENS];
String itemsTaskId[MAX_ITENS];

int totalTasks = 0;
int totalEvents = 0;

// VARIÁVEIS PARA SELEÇÃO

int tarefaSelecionada = 0;
int primeiraTarefaVisivel = 0;

// VARIAVEIS DE CONTROLE

unsigned long ultimaAtualizacao = 0;
unsigned long ultimoBotaoSelecionar = 0;
unsigned long ultimoBotaoConcluir = 0;

void setup() {

Serial.begin(115200);
Wire.begin();

if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {

Serial.println(“Falha ao iniciar OLED”);
while (true) {
delay(10);
}
}

display.clearDisplay();
display.setTextColor(
SSD1306_WHITE);

display.setTextSize(1);
display.setCursor(0, 0);
display.println(
“Conectando WiFi…”);

WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(“.”);
}
Serial.println();
Serial.println(“WiFi conectado!”);
Serial.println(WiFi.localIP());

display.display();

pinMode(BOTAO_SELECIONAR, INPUT_PULLUP);
pinMode(BOTAO_CONCLUIR, INPUT_PULLUP);

buscarDados();
mostrarLista();
}

void loop() {

unsigned long agora = millis();
if (agora – ultimaAtualizacao >= INTERVALO_ATUALIZACAO) {
buscarDados();
ultimaAtualizacao = agora;
mostrarLista();

}

if (
digitalRead(BOTAO_SELECIONAR) == LOW && agora – ultimoBotaoSelecionar >= DEBOUNCE) {
ultimoBotaoSelecionar = agora;
selecionarProximaTarefa();
}

if (
digitalRead(BOTAO_CONCLUIR) == LOW && agora – ultimoBotaoConcluir >= DEBOUNCE) {
ultimoBotaoConcluir = agora;
concluirTarefa();
}
}

void buscarDados() {

if (WiFi.status() != WL_CONNECTED) {
return;
}

WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);

http.begin(client, SCRIPT_URL);
int httpCode = http.GET();

if (httpCode == 200) {

String payload = http.getString();

Serial.println(“Resposta:”);
Serial.println(payload);

DynamicJsonDocument doc(8192);

DeserializationError err = deserializeJson(doc, payload);

if (!err) {

totalTasks = 0;
totalEvents = 0;
JsonArray items = doc[“items”].as<JsonArray>();

for (JsonObject item : items) {

String type = item[“type”].as<String>();
if (type == “task” && totalTasks < MAX_ITENS) {

itemsType[totalTasks] = “task”;
itemsTitle[totalTasks] = item[“title”].as<String>();
itemsListId[totalTasks] = item[“listId”].as<String>();
itemsTaskId[totalTasks] = item[“taskId”].as<String>();

totalTasks++;
}
}

for (JsonObject item : items) {

String type = item[“type”].as<String>();
if (type == “event” && totalTasks + totalEvents < MAX_ITENS) {
int pos = totalTasks + totalEvents;

itemsType[pos] = “event”;
itemsTitle[pos] = item[“title”].as<String>();
itemsTime[pos] = item[“time”].as<String>();
totalEvents++;
}
}

// RESETAR SELEÇÃO
if (tarefaSelecionada >= totalTasks) {
tarefaSelecionada = totalTasks > 0 ? totalTasks – 1 : 0;
}

ajustarScroll();
Serial.printf(“Tarefas: %d | Eventos: %d\n”, totalTasks, totalEvents);

} else {

Serial.print(“Erro JSON: “);
Serial.println(err.c_str());
}

} else {
Serial.printf(“Erro HTTP: %d\n”, httpCode);
}
http.end();
}

void selecionarProximaTarefa() {

if (totalTasks == 0) {
return;
}

tarefaSelecionada++;

if (
tarefaSelecionada >= totalTasks) {
tarefaSelecionada = 0;
}

ajustarScroll();
mostrarLista();
}

void ajustarScroll() {

const int LINHAS_VISIVEIS = 5;

if (tarefaSelecionada < primeiraTarefaVisivel) {
primeiraTarefaVisivel = tarefaSelecionada;
}

if (tarefaSelecionada >= primeiraTarefaVisivel + LINHAS_VISIVEIS) {
primeiraTarefaVisivel = tarefaSelecionada – LINHAS_VISIVEIS + 1;
}

if (primeiraTarefaVisivel < 0) {
primeiraTarefaVisivel = 0;
}
}

void concluirTarefa() {
if (totalTasks == 0) {
return;
}

int indice = tarefaSelecionada;

if (indice < 0 || indice >= totalTasks) {
return;
}

Serial.println(“Concluindo tarefa:”);
Serial.println(itemsTitle[indice]);

// CONSTRUIR URL

String url = String(SCRIPT_URL);

url += “&action=complete”;
url += “&listId=”;
url += itemsListId[indice];
url += “&taskId=”;
url += itemsTaskId[indice];
Serial.println(url);

WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
http.begin(client, url);
int httpCode = http.GET();

if (httpCode == 200) {

String resposta = http.getString();
Serial.println(“Resposta conclusão:”);
Serial.println(resposta);

// REMOVE IMEDIATAMENTE DA LISTA
for (int i = indice; i < totalTasks – 1; i++) {

itemsTitle[i] = itemsTitle[i + 1];
itemsListId[i] = itemsListId[i + 1];
itemsTaskId[i] = itemsTaskId[i + 1];
itemsType[i] = itemsType[i + 1];
}

totalTasks–;

if (
totalTasks == 0) {
tarefaSelecionada = 0;
primeiraTarefaVisivel = 0;
mostrarSemTarefas();
} else {
if (tarefaSelecionada >= totalTasks) {
tarefaSelecionada = totalTasks – 1;
}
ajustarScroll();
mostrarLista();
}

} else {
Serial.printf(“Erro ao concluir tarefa: %d\n”, httpCode);
}
http.end();
}

void mostrarLista() {
display.clearDisplay();
display.setTextSize(1);
display.setTextWrap(false);

// SEM TAREFAS
if (totalTasks == 0) {
if (totalEvents == 0) {
mostrarSemTarefas();
return;
}
}

// LISTA
int y = 0;
const int LINHAS_VISIVEIS = 5;

int ultimaLinha = min(totalTasks, primeiraTarefaVisivel + LINHAS_VISIVEIS);

for (int i = primeiraTarefaVisivel; i < ultimaLinha; i++) {
display.setCursor(0, y);

// SELEÇÃO
if (i == tarefaSelecionada) {
display.print(“> “);
} else {
display.print(” “);
}
// NOME DA TAREFA

String titulo =
itemsTitle[i];

// Limitar para caber no OLED
if (
titulo.length() > 20) {
titulo =
titulo.substring(0, 20);
titulo += “…”;
}

display.println(titulo);
y += 10;
}

// EVENTOS
if (totalEvents > 0) {
int eventPos = totalTasks – primeiraTarefaVisivel;

// Se houver espaço
if (eventPos < 6) {

for (int e = 0; e < totalEventse++) {
if (y > 56) {
break;
}
display.setCursor(0, y);
display.print(“[evento] “);
String titulo = itemsTitle[totalTasks + e];

if (titulo.length() > 12) {
titulo =
titulo.substring(0, 12);
titulo += “…”;
}

display.println(titulo);
y += 10;
}
}
}

display.display();
}

void mostrarSemTarefas() {

for (int frame = 0; frame < 3; frame++) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(25, 15);
display.println(“SEM TAREFAS”);
// animação
display.setCursor(40, 35);

if (frame == 0) {
display.println(“Tudo certo!”);
} else if (frame == 1) {
display.println(“Tudo certo!.”);
} else {
display.println(“Tudo certo!..”);
}
display.display();
delay(300);
}
}

 

Código Google

 

// ============================================================
//  OLED Calendar - Backend (Google Apps Script)
// ============================================================
//  O que este script faz:
//   - Busca tarefas pendentes do Google Tasks (com id, para poder
//     marcá-las como concluídas depois)
//   - Busca os próximos eventos do seu Google Calendar padrão
//   - Busca aniversário do dia (calendário automático do Google)
//   - Devolve tudo em JSON para o ESP8266 consumir via HTTP
//   - Recebe pedidos de "marcar tarefa como concluída" vindos do
//     Arduino (quando a pessoa aperta o botão 2 na tarefa selecionada)
//
//  COMO CONFIGURAR (passo a passo):
//   1) Acesse https://script.google.com/ e crie um novo projeto
//   2) Apague o conteúdo padrão e cole todo este arquivo
//   3) Ative a API de Tasks:
//        No editor, clique em "Serviços" (ícone de +) no menu lateral
//        Procure "Tasks API" e clique em "Adicionar"
//   4) Troque o valor de API_KEY abaixo por uma chave secreta sua
//   5) Clique em "Implantar" > "Nova implantação"
//        Tipo: "App da Web"
//        Executar como: "Eu"
//        Quem pode acessar: "Qualquer pessoa"
//   6) Autorize as permissões pedidas (Calendar e Tasks)
//   7) Copie a URL gerada (termina em /exec) — essa é a SCRIPT_URL
//      do Arduino, lembrando de adicionar "?key=SUACHAVE" no final
//   8) Toda vez que editar este código, lembre de criar uma NOVA
//      VERSÃO da implantação (Implantar > Gerenciar implantações >
//      ✏️ > Nova versão > Implantar) para as mudanças valerem
// ============================================================

var API_KEY = "1234"; // <-- deve ser igual ao ?key= usado no Arduino
var MAX_ITEMS = 8;       // quantas tarefas/eventos enviar no total
var HORAS_A_FRENTE = 48; // buscar eventos das próximas X horas

function doGet(e) {
  // Segurança simples via parâmetro ?key=
  // (a checagem "!e || !e.parameter" evita erro ao testar a função
  //  direto no editor, onde "e" vem undefined)
  if (!e || !e.parameter || !e.parameter.key || e.parameter.key !== API_KEY) {
    return ContentService.createTextOutput(
      JSON.stringify({ error: "unauthorized" })
    ).setMimeType(ContentService.MimeType.JSON);
  }

  // ---- Ação de concluir tarefa (chamada pelo botão 2 do Arduino) ----
  if (e.parameter.action === "complete") {
    return concluirTarefa(e.parameter.listId, e.parameter.taskId);
  }

  // ---- Ação padrão: listar tarefas + eventos + aniversário ----
  var tarefas = [];
  var eventos = [];

  // Tarefas do Google Tasks (com id + listId, necessários pra concluir depois)
  // Requer o serviço avançado "Tasks API" ativado (ver instruções acima)
  try {
    var taskLists = Tasks.Tasklists.list().items;
    if (taskLists) {
      taskLists.forEach(function (list) {
        var result = Tasks.Tasks.list(list.id, {
          showCompleted: false,
          maxResults: 20
        });
        if (result.items) {
          result.items.forEach(function (t) {
            tarefas.push({
              type: "task",
              title: t.title,
              listId: list.id,
              taskId: t.id
            });
          });
        }
      });
    }
  } catch (err) {
    // Se a API de Tasks não estiver ativada corretamente, ignora e
    // segue só com os eventos do Calendar
  }

  // Eventos do Google Calendar
  var now = new Date();
  var future = new Date(now.getTime() + HORAS_A_FRENTE * 60 * 60 * 1000);
  var eventosCalendar = CalendarApp.getDefaultCalendar().getEvents(now, future);
  eventosCalendar.forEach(function (ev) {
    eventos.push({ type: "event", title: ev.getTitle() });
  });

  // Tarefas sempre antes dos eventos
  var items = tarefas.concat(eventos).slice(0, MAX_ITEMS);

  return ContentService.createTextOutput(
    JSON.stringify({ items: items, aniversariante: buscarAniversarioHoje() })
  ).setMimeType(ContentService.MimeType.JSON);
}

// ---- Marca uma tarefa como concluída no Google Tasks ----
function concluirTarefa(listId, taskId) {
  try {
    if (!listId || !taskId) {
      return ContentService.createTextOutput(
        JSON.stringify({ ok: false, erro: "listId ou taskId ausente" })
      ).setMimeType(ContentService.MimeType.JSON);
    }
    Tasks.Tasks.patch({ status: "completed" }, listId, taskId);
    return ContentService.createTextOutput(
      JSON.stringify({ ok: true })
    ).setMimeType(ContentService.MimeType.JSON);
  } catch (err) {
    return ContentService.createTextOutput(
      JSON.stringify({ ok: false, erro: String(err) })
    ).setMimeType(ContentService.MimeType.JSON);
  }
}

// ---- Aniversário do dia (calendário automático "Aniversários" do Google) ----
function buscarAniversarioHoje() {
  try {
    var nomesCalendario = ["Aniversários", "Contacts' birthdays", "Birthdays"];
    var cals = [];
    for (var i = 0; i < nomesCalendario.length; i++) {
      cals = CalendarApp.getCalendarsByName(nomesCalendario[i]);
      if (cals.length > 0) break;
    }
    if (cals.length === 0) return null;

    var hoje = new Date();
    var inicio = new Date(hoje.getFullYear(), hoje.getMonth(), hoje.getDate());
    var fim = new Date(inicio.getTime() + 24 * 60 * 60 * 1000);

    for (var c = 0; c < cals.length; c++) {
      var eventosDia = cals[c].getEvents(inicio, fim);
      if (eventosDia.length > 0) {
        var titulo = eventosDia[0].getTitle();
        var match = titulo.match(/^Aniversário de (.+)$/i) || titulo.match(/^(.+)'s birthday$/i);
        return match ? match[1] : titulo;
      }
    }
  } catch (err) {
    // Sem calendário de aniversários disponível/autorizado — segue sem aniversário
  }
  return null;
}

 

Publicidade

Loja
Compartilhar:

Leia também