Manual

do

Maker

.

com

LilyGo T Wristband com ESP32

LilyGo T Wristband com ESP32

Quase sempre a LilyGo tem produtos novos e desses, a maioria é impactante! É o caso do wristband com ESP32, que não deixa nada a desejar! Um bracelete 100% programável, com diversos recursos acessíveis para programação, permitindo transformar esse gadget em algo realmente útil!

Recursos do T Wristband com ESP32

Primeiramente, o próprio processador, que é um ESP32 e intrinsecamente já devemos considerar bluetooth e WiFi nativos. Além disso, ela possui um IMU de 9 eixos, a MPU9250, que podemos usar para fazer controle do sono. Ok, isso já existe pronto, apesar de certamente não ser tão barato quanto um wristband com ESP32 da LilyGo, mas não é só isso.

Essa wristband com ESP32 tem também um RTC PCF8563 (claro que haveria de ter) para fazermos um relógio. Agora já temos um relógio e um IMU.

Para interagir com ela, claro que precisamos de um botão; ou um sensor de toque. No caso, o TTP223, que dispensa qualquer recurso extra, bastando monitorar o pino de IO33.

Usar WiFi e bluetooth consumirão a bateria em um tempo muito menor, portanto uma programação estratégica se faz necessária. Por falar nisso, a bateria tem 80mAh, suficiente para algumas boas horas de trabalho. E claro, é recarregável.

Acessórios da T Wristband com ESP32

t-wristband_acessorios.webp

Ela vem com um um acoplador para recarga, bastando desencaixá-la da pulseira e conectá-la à porta USB do computador. A carga é bastante rápida, considerando a bateria. Além disso, ela vem com uma placa de programação. Basta abrir o encapsulamento, retirar o circuito e conectá-lo ao flat da placa de interface. Horrível, para quem deseja subir diversos firmwares para teste, não? Bem, não se preocupe. Podemos posteriormente fazer upload por OTA. Nesse artigo em particular, veremos como fazer o upload por OTA através do browser, de forma a estarmos livres da biblioteca Arduino OTA e de sua interface de programação horrível.

No vídeo desse link você já poderá contemplar os pormenores do gadget.

Se assistiu o vídeo antes de seguir a leitura, presumo que possa lhe ter despertado um interesse maior, pois as ideias devem estar pipocando em sua cabeça. É um hardware incrível!

Implementação de código na T Wristband com ESP32

Tem três formas principais de desenvolver. A LilyGo sempre disponibiliza os recursos para sua placa, de modo que não passamos sufoco para desenvolver. Mas em cada qual há uma contrapartida.

Programação em blocos para T Wristband com ESP32

Isso mesmo, essa pulseira pode ser programada através do KBIDE, cuja interface já discorri a respeito nesse outro artigo. A LilyGo adicionou suporte à placa nessa IDE, bastando ir até o menu de placas e adicioná-la. Porém, entretanto e todavia, a flexibilidade de programação é menor. Mas para quem não é desenvolvedor e quer resultados rápidos, talvez seja a melhor pedida.

Repositório git da T Wristband

Outra opção é clonar o repositório do produto e a partir do sketch de exemplo, fazer suas próprias implementações. Particularmente tive problemas com bibliotecas, pois o ambiente de desenvolvimento da LilyGo certamente é Windows e com a IDE porca do Arduino. Eu uso Linux com Visual Studio Code e PlatformIO. Para mim foi ótimo, pude aproveitar parte do código da LilyGo, parte de um código que encontrei para OTA e parte de implementação própria para um projeto que tem que ficar pronto em dois dias para não me atrasar com outras tarefas. Então já adianto, você vai por pra funcionar rápido e sem sofrimento.

A contrapartida é que o OTA utilizado pela LilyGo é baseada no Arduino OTA, de forma que você será obrigado a usar a IDE do Arduino (eca).

Programar a T Wristband com Visual Studio Code e PlatformIO

Essa me parece ser a melhor opção. O Visual Studio Code tem todos os recursos que um programador pode precisar; é fácil expandir macros, simplesmente parando o mouse sobre o nome. É fácil acessar os métodos de uma classe, simplesmente adicionando um ponto (".") adiante do objeto estático instanciado. Auto-completion é fundamental para desenvolvimento ágil, assim como acessar headers e corpo de bibliotecas, simplesmente segurando Enter e clicando sobre uma referência da classe. Se me permite um palpite, anime-se a experimentar o Visual Studio Code com PlatformIO. Se não gostar, descarte, mas não acomode-se na arcaica IDE do Arduino. O plugin PlatformIO você instala pela própria IDE do Visual Studio Code. Para baixá-lo, siga esse link.

A contrapartida de programar sua T Wristband com ESP32 dessa maneira será experimentar algo novo, o que pode exigir de você uma meia hora de esforço. Não mais; provavelmente menos. Também não implementei todos os recursos, pois no momento preciso do relógio, do OTA e (ainda por implementar) bluetooth, para o projeto supracitado. Farei um "trocador de slides" para facilitar a vida na palestra que farei no Arduino Day UNIVAG, em Mato Grosso.

Uma vantagem desse método é que nesse código implementei OTA através do browser, praticamente através de um Ctrl+Chups que achei em algum canto da Internet. Como fiz um Frankenstein pela pressa, peguei parte do código diretamente no sketch da LilyGo e, sem muita esperança de funcionar, peguei esse OTA em outro lugar. Tenho o costume de implementar, compilar e tratar os erros, quando uso código já escrito, porque não tenho paciência pra entender código dos outros - e por isso prefiro escrever do zero.

Código da T Wristband com ESP32

O que aproveitei da LilyGo foi a data, hora e medição da bateria, além de alguns defines. Nesse primeiro momento é o suficiente.

Criei duas tasks, que em sistemas operacionais de tempo real funcionam como threads. Desse modo tenho um funcionamento assíncrono de processos, mas certamente adicionei mais uma ou duas para atender meu projeto. Também aproveitei uma parte dos recursos do artigo sobre o TTGO T-Watch, que escrevi há pouco tempo. O código inicial para a T Wristband esse:

#include <Arduino.h>
#include <WiFi.h>
#include <Wire.h>
#include <TFT_eSPI.h>
#include <ESPmDNS.h>
#include <Rtc_Pcf8563.h>
#include <Update.h>
#include <ESPmDNS.h>
#include <WebServer.h>

#include "/home/djames/.platformio/lib/TFT_eSPI_ID1559/examples/320 x 240/Free_Font_Demo/Free_Fonts.h"

#define TP_PIN_PIN          33
#define I2C_SDA_PIN         21
#define I2C_SCL_PIN         22
#define IMU_INT_PIN         38
#define RTC_INT_PIN         34
#define BATT_ADC_PIN        35
#define VBUS_PIN            36
#define TP_PWR_PIN          25
#define LED_PIN             4
#define CHARGE_PIN          32

const char* hostn       = "esp32";
const char* ap_ssid     = "Wristband";
const char* ap_password = "030041975";
const char* sta_ssid    = "SuhankoFamily";
const char* sta_passwd  = "fsjmr112";
bool doit            = true;
bool rtcIrq          = false;
bool initial         = 1;

TFT_eSPI tft         = TFT_eSPI();

long int timeoutGot  = millis();

uint8_t screenNumber = 0;
uint8_t hh, mm, ss   = 0;
uint8_t omm          = 99;
uint8_t xcolon       = 0;

uint32_t colour      = 0;
uint32_t targetTime  = 0;

int vref             = 1100;

int pressed_time = 0;

/* Style */
String style =
"<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
"input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
"#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
"#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
"form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
".btn{background:#3498db;color:#fff;cursor:pointer}</style>";

/* Login page */
String loginIndex = 
"<form name=loginForm>"
"<h1>ESP32 Login</h1>"
"<input name=userid placeholder='User ID'> "
"<input name=pwd placeholder=Password type=Password> "
"<input type=submit onclick=check(this.form) class=btn value=Login></form>"
"<script>"
"function check(form) {"
"if(form.userid.value=='admin' && form.pwd.value=='admin')"
"{window.open('/serverIndex')}"
"else"
"{alert('Error Password or Username')}"
"}"
"</script>" + style;
 
/* Server Index Page */
String serverIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>"
"<label id='file-input' for='file'>   Choose file...</label>"
"<input type='submit' class=btn value='Update'>"
"<br><br>"
"<div id='prg'></div>"
"<br><div id='prgbar'><div id='bar'></div></div><br></form>"
"<script>"
"function sub(obj){"
"var fileName = obj.value.split('\\\\');"
"document.getElementById('file-input').innerHTML = '   '+ fileName[fileName.length-1];"
"};"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
"$.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"$('#bar').css('width',Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!') "
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>" + style;

Rtc_Pcf8563 rtc;

WebServer server(80);

void RTC_Show();
void dream();
void setRTCtime();

void setRTCtime(){
    rtc.initClock();
    rtc.setDate(28,6,2,0,20);
    rtc.setTime(13,35,0);
}

String getVoltage(){
    uint16_t v            = analogRead(BATT_ADC_PIN);
    float battery_voltage = ((float)v / 4095.0) * 2.0 * 3.3 * (vref / 1000.0);
    return String(battery_voltage) + "V";
}

void testDisplay(void *pvParameters){
    while (true){
        tft.fillScreen(TFT_BLACK);
        tft.setTextColor(TFT_WHITE);

        if (doit){
            RTC_Show();
            Serial.println(rtc.formatTime());
            doit = false;
            vTaskDelay(pdMS_TO_TICKS(100));
            
        }
        else{  
            tft.drawString("Access Point: ",  20, tft.height() / 2 - 20);
            tft.drawString("\"Wristband\"",  40, tft.height() / 2 );
            
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

void touchMonitor(void *pvParameters){
    while (true){
        uint8_t tp_state = digitalRead(TP_PIN_PIN);
        doit = tp_state > 0 ? true : false;
        vTaskDelay(pdMS_TO_TICKS(10));
    }  
}

void RTC_Show(){
    pressed_time = millis();
    while (digitalRead(TP_PIN_PIN)){
         if (doit){
            hh = rtc.getHour();
            mm = rtc.getMinute();
            //Serial.println("OTA (Over The Air) is working");
        }
        if ((millis() - pressed_time) > 3000){
            dream();
        }
        if (targetTime < millis()) {
            targetTime = millis() + 1000;
            if (ss == 0 || initial) {
                initial = 0;
                tft.setTextColor(TFT_GREEN, TFT_BLACK);
                tft.setCursor (8, 60);
                tft.print(__DATE__); // This uses the standard ADAFruit small font
            }

            tft.setTextColor(TFT_BLUE, TFT_BLACK);
            tft.drawCentreString(getVoltage(), 120, 60, 1); // Next size up font 2


            // Update digital time
            uint8_t xpos = 6;
            uint8_t ypos = 0;
            //if (omm != mm) { // Only redraw every minute to minimise flicker
            if (true){
                // Uncomment ONE of the next 2 lines, using the ghost image demonstrates text overlay as time is drawn over it
                tft.setTextColor(0x39C4, TFT_BLACK);  // Leave a 7 segment ghost image, comment out next line!
                //tft.setTextColor(TFT_BLACK, TFT_BLACK); // Set font colour to black to wipe image
                // Font 7 is to show a pseudo 7 segment display.
                // Font 7 only contains characters [space] 0 1 2 3 4 5 6 7 8 9 0 : .
                tft.drawString("88:88", xpos, ypos, 7); // Overwrite the text to clear it
                tft.setTextColor(0xFBE0, TFT_BLACK); 
                omm = mm;

                if (hh < 10) xpos += tft.drawChar('0', xpos, ypos, 7);
                xpos += tft.drawNumber(hh, xpos, ypos, 7);
                xcolon = xpos;
                xpos += tft.drawChar(':', xpos, ypos, 7);
                if (mm < 10) xpos += tft.drawChar('0', xpos, ypos, 7);
                    tft.drawNumber(mm, xpos, ypos, 7);
            }

            if (ss % 2) { // Flash the colon
                tft.setTextColor(0x39C4, TFT_BLACK);
                xpos += tft.drawChar(':', xcolon, ypos, 7);
                tft.setTextColor(0xFBE0, TFT_BLACK);
            } 
            else {
                tft.drawChar(':', xcolon, ypos, 7);
            }
        }
    }

}

void dream(){
    uint8_t xpos = 6;
    uint8_t ypos = 0;
    tft.setTextColor(0x39C4, TFT_BLACK);
    tft.drawString("88:88", xpos, ypos, 7);
    vTaskDelay(pdMS_TO_TICKS(100));
    tft.setTextColor(0x39C4, TFT_BLACK);
    tft.drawString("OFF", xpos, ypos, 7);
    vTaskDelay(pdMS_TO_TICKS(2000));
  
    esp_sleep_enable_ext1_wakeup(GPIO_SEL_33, ESP_EXT1_WAKEUP_ANY_HIGH);
    esp_deep_sleep_start();
}

void setup() {
    tft.init();
    tft.setRotation(1);
    Serial.begin(9600);
    Wire.setClock(400000);

    pinMode(TP_PIN_PIN,INPUT);
    pinMode(TP_PWR_PIN, PULLUP);
    pinMode(RTC_INT_PIN, INPUT_PULLUP);
    pinMode(CHARGE_PIN, INPUT_PULLUP);

    Serial.print("Setting AP (Access Point)…");
    WiFi.mode(WIFI_AP_STA);
    WiFi. begin(sta_ssid, sta_passwd);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(" (wifi) ");
    }
    WiFi.softAP(ap_ssid, ap_password);

    

    if (!MDNS.begin(hostn)) { //http://esp32.local
        Serial.println("Error setting up MDNS responder!");
        while (1) {
            delay(1000);
        }
    }

    Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();

    //IPAddress IP = WiFi.softAPIP();
    //Serial.print("AP IP address: ");
    //Serial.println(IP);

    setRTCtime();

    xTaskCreatePinnedToCore(testDisplay,"testDisplay", 10000, NULL, 1, NULL,0);
    xTaskCreatePinnedToCore(touchMonitor,"touchMonitor", 10000, NULL, 1, NULL,0);

    Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
    Wire.setClock(400000);
  
}

void loop(){
 server.handleClient();
  delay(1);
}

Nesse código já está implementado o modo STA+AP, mas ainda não há razão para conectar-se ao modo AP. Para acessar a interface de upload do firmware, use http://esp32.local. Esse recurso de resolução de nomes é provido pela biblioteca ESPmDNS.

O usuário e senha padrão é admin, podendo ser trocada no código. Basta utilizar o Ctrl+F e procurar por admin.

As credenciais de acesso AP também podem ser modificadas e as credenciais do modo STA são da minha rede de laboratório.

Se tocar no sensor de toque, aparecerá a hora. Se mantiver o toque por 3 segundos, o relógio ficará cinza. Nesse momento, tire o dedo do sensor para que a Wristband possa entrar em modo deep sleep. Para retomar, toque no sensor novamente.

Onde comprar a T Wristband com ESP32?

Infelizmente hoje temos apenas a opção de importação. Você pode adquiri-la pelo Aliexpress, através desse link.

Vídeo da T Wristband com ESP32

Vou fazer um vídeo breve para demonstrar essa implementação. Se não é inscrito, inscreva-se em nosso canal DobitaobyteBrasil no Youtube e clique no sininho para receber notificações.

 

Revisão: Ricardo Amaral de Andrade

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.