Aprenda
Como usar o Google Calendar no ESP
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;
}