Color picker com ESP32 – Heltec Stick

Existe uma razão muito justa para esse artigo existir, mas não posso revelar ainda. Então, por mais que pareça estranho, ou por mais que ache simples o tema, acompanhe o artigo… tenho certeza que no próximo artigo relacionado fará todo o sentido. Para não parecer algo extremamente simplório, vamos fazer esse color picker com ESP32 enviar as cores para um programa em Python, assim fica mais divertido.

Color picker digital

A maioria (se não todos os) dos programas gráficos tem um color picker. Essa ferramenta é fundamental para quando queremos uma amostra de cores de uma imagem. A materialização de um color picker tem um propósito maior, que é trazer uma cor que está no mundo real para dentro da tela do computador, por exemplo. Essa pode ser uma ótima ferramenta para designers, decoradores, arquitetos etc.

Nesse artigo, o processo será feito com a Heltec Stick, disponível na curto circuito por esse link. Para capturar as cores, utilizaremos o sensor TCS34725, também disponível na CurtoCircuito.

Recursos a implementar no color picker com ESP32

Essa placa Heltec Stick tem LoRa, portanto serve para montes de aplicações, mas por uma razão específica, a comunicação entre o computador e a placa será por WiFi.

A leitura da cor é RGB, que deverá ser convertida para CMYK antes de ser enviada. Também há uma razão para isso, mas não é aplicável para esse artigo.

Para que seja um dispositivo móvel, será necessário alimentá-lo por bateria. Nada melhor do que esse adaptador da Saravati.

Posteriormente farei um case em acrílico 100% cast da Sinteglas, mas o objetivo agora é exclusivamente provar o conceito.

Especificações da placa Heltec Stick ESP32

O modelo que tenho em mãos é LoRa 868 certificado, com chip sx1276/sx1278. Processador ESP32, que nos dá toda a flexibilidade do FreeRTOS (exceto o TCP+).

A interface ainda é micro USB (algumas placas de outros fabricantes já estão saindo com USB-C), com um regulador de tensão completo, proteção ESD e contra curto, blindagem de RF e outras medidas de proteção. Essa placa é caprichada!

Ela possui uma interface de bateria, da qual farei uso nesse projeto. E foi por essa razão que essa placa foi escolhida; tamanho diminuto e mobilidade. A interface de bateria tem um sistema de gerenciamento para carga e descarga, sobrecarga, detecção de alimentação por bateria (fazendo a troca automática da alimentação entre USB e bateria). Encantadora!

Como todo ESP32, tem bluetooth e WiFi. Com o LoRa, são 3 redes disponíveis.

Poderia ter parado por aí, mas essa placa oferece mais; um display integrado de 0.49′ 64×32 OLED, sendo útil para fins de debug, informações de bateria e/ou outros.

O controlador USB é um CP2102, reconhecido imediatamente pelo Linux (não saberei informar sobre o Windows, mas acredito que seja tão direto quanto).

Essa placa é totalmente suportada pela IDE do Arduino, bastando incluir a URL json em File > Preferences.

https://resource.heltec.cn/download/package_heltec_esp32_index.json

Depois, Em Tools > Board Manager pesquise por Heltec ESP32:

Color picker com ESP32 - biblioteca Heltec

A placa estará pronta para uso!

Com exclusividade, a Heltec oferece suporte a um protocolo LoRaWAN que permite se comunicar com qualquer gateway/estação base. Como requerimento, o número de ativação serial será necessário. Para isso, após estar com a placa em mãos acesse essa página.

Abra o sketch de exemplo em Arquivo > Exemplos > ESP32 > ChipID. Suba-o e abra o monitor serial. A informação será exibida periodicamente. Eu já estou com minha licença!

A biblioteca LoRaWAN estará disponível para download no github.

Recursos e documentação da placa podem ser pegos diretamente no site da Heltec.

No gerenciador de bibliotecas da IDE do Arduino, podemos encontrar a biblioteca para ESP32, contendo alguns bons exemplos:

Recomendo o cadastro da placa para obter a licença, pois principalmente para quem está iniciando em LoRaWAN, é muito auxiliador para entender um pouco da prática. Ao abrir um dos sketches de LoRaWAN, bastará adicionar sua licença para poder usar o sketch. A placa que tenho em mãos está marcada como 868, mas tem em outras frequências. Tentei ingressar em EU868 e EU915, mas pelo visto não tenho gateways na região.

Para concluir a apresentação da placa, aqui está o pinout:

Heltec Stick Pinout - Color picker com ESP32

Feitas as apresentações, bora começar o projeto!

Sensor de cores RGB TCS34725


Não foi à toa que escolhi o TCS34725 para fazer o color picker com ESP32. Esse sensor tem uma precisão bastante satisfatória. Porém, sempre deve-se atentar à influência de luz externa. Por isso que devemos isolar ao máximo a região a ser lida. A conexão é simples e o sensor opera em 3v3 ou 5v, basta escolher no pinout. Para o ESP32, utilize 3v3.

Utilizei a biblioteca TCS34725 disponível no repositório de bibliotecas do Arduino, mas para não dizer que foi tudo perfeito – eu tinha soldado do lado errado uma barra de conexão fêmea. Como precisava ser do outro lado e meus jumpers são todos macho-fêmea agora, tive que remover. O problema é que fritei a placa toda pra conseguir remover o slot e não sei se de alguma forma influenciei a leitura, mas a amostragem RGB parece incrivelmente satisfatória. Já a conversão para CMYK na IDE do Arduino está estranha, não sei se pode ser algo relacionado ao compilador, mas usei a mesma função que no código do projeto que apresentarei no próximo artigo. Enfim, o problema está exclusivamente relacionado à quantidade de preto, mas não será um problema.

A conversão CMYK deve ser enviada para um dispositivo remoto – para esse artigo, o programa feito em Qt. Antes de fazer o programa, fiz um socket server com Python:

import socket
HOST = '192.168.1.200'              # Endereco IP do Servidor
PORT = 1234            # Porta que o Servidor esta
tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
orig = (HOST, PORT)
tcp.bind(orig)
tcp.listen(1)
while True:
    con, client = tcp.accept()
    print( 'Client: ', client)
    while True:
        msg = con.recv(7)
        if not msg: break
        #print( client, msg)
        for i in range(len(msg)):
            print(msg[i])
    print( 'Closing connection to: ', client)
    con.close()

Basta deixá-lo rodando e pegar amostras no sensor de cores.
O wiring do sensor de cores é simples: 3v3, GND, 23 e 22. Só se guiar pelo pinout disposto mais acima.

O código para a captura é esse:

#include <WiFi.h>
#include <Wire.h>
#include "heltec.h"
#include "TCS34725.h"

#define OLED_UPDATE_INTERVAL 500  

const char* ssid       = "SuhankoFamily"; 
const char* password   = "fsjmr112"; 

const uint16_t port = 1234;
const char * host = "192.168.1.209"; // TODO: passar para o modo AP

TCS34725 tcs;
unsigned long interval = millis();
int timeout            = 3000;
int values[4]       = {0,0,0,0};
unsigned char RGBsample[3]   = {0,0,0};
 
void rgb2cmyk(uint8_t R, uint8_t G, uint8_t B){
  float Rfrac = (float)R/255.0;
  float Gfrac = (float)G/255.0;
  float Bfrac = (float)B/255.0;

  float K = 1-max({Rfrac,Gfrac,Bfrac});

  float C = (1-Rfrac-K)/(1-K);
  float M = (1-Gfrac-K)/(1-K);
  float Y = (1-Bfrac-K)/(1-K);

  values[0] = C*100;
  values[1] = M*100;
  values[2] = Y*100;
  values[3] = K*100/2; //GAMBIARRA PARA ACERTAR O NIVEL DE PRETO. PRECISA DE CORREÇÃO.
}

void setupWIFI()
{
  Heltec.display->clear();
  Heltec.display->drawString(0, 0, "Connecting...");
  Heltec.display->drawString(0, 10, String(ssid));
  Heltec.display->display();

  WiFi.disconnect(true);
  delay(1000);

  WiFi.mode(WIFI_STA);
  //WiFi.onEvent(WiFiEvent);
  WiFi.setAutoConnect(true);
  WiFi.setAutoReconnect(true);    
  //WiFi.setHostname(HOSTNAME);
  WiFi.begin(ssid, password);

  byte count = 0;
  while(WiFi.status() != WL_CONNECTED && count < 10)
  {
    count ++;
    delay(500);
    Serial.print(".");
  }

  Heltec.display->clear();
  if(WiFi.status() == WL_CONNECTED){
    Heltec.display->drawString(0, 0, "Conectado");
    Heltec.display->drawString(0, 10, "Ate logo");
    Heltec.display->display();
    delay(5000);
    Heltec.display->clear();
     Heltec.display->display();
  }
  else{
    Heltec.display->drawString(0, 0, "Connect False");
    Heltec.display->display();
  }

  Heltec.display->clear();
}

void setup()
{
    pinMode(0,INPUT_PULLDOWN);
    Serial.begin(115200);
    Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
    pinMode(25, OUTPUT);

    while (!Serial) {
        ;
    }

    setupWIFI();
  
    Wire.begin(23,22);
    if (!tcs.attach(Wire)) Serial.println("ERROR: TCS34725 NOT FOUND !!!");

    tcs.integrationTime(33); // ms
    tcs.gain(TCS34725::Gain::X01);

}

void loop(){
    if (!digitalRead(0)){
        WiFiClient client;
        Serial.println("INTERRUPCAO");
        digitalWrite(25,HIGH);
        delay(1500);
        digitalWrite(25,LOW);

        Heltec.display->clear();
        Heltec.display->setFont(ArialMT_Plain_10);
        Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
      
        Heltec.display->drawString(0, 0, String("Address"));
        Heltec.display->display();

        if (tcs.available()){
           TCS34725::Color color = tcs.color();
          if (millis()-interval > timeout){
              RGBsample[0] = color.r;
              RGBsample[1] = color.g;
              RGBsample[2] = color.b;

              rgb2cmyk(RGBsample[0],RGBsample[1],RGBsample[2]);
              
              Serial.print("R          : "); Serial.println(RGBsample[0]);
              Serial.print("G          : "); Serial.println(RGBsample[1]);
              Serial.print("B          : "); Serial.println(RGBsample[2]);
              interval = millis();

              if (!client.connect(host, port)){
                  Serial.println("Connection to host failed");
              }
 
              unsigned char msg[6];
              memset(msg,0,sizeof(msg));
              msg[0] = 0x5e;
              msg[1] = values[0];
              msg[2] = values[1];
              msg[3] = values[2];
              msg[4] = values[3];
              msg[5] = '$';
              Serial.print("msg: ");
              for (uint8_t i=0;i<6;i++){
                  Serial.write(msg[i]);  
              }
              Serial.println(" ");
              
              Serial.print("CMYK: ");
              for (uint8_t j=0;j<4;j++){
                Serial.print(values[j]);
                Serial.print(" ");
              }
              Serial.println(" ");
              
              client.write(&msg[0],7);
              client.stop();
          }
       }
    }
}

void printRGB(){
  Heltec.display->clear();
  Heltec.display->setFont(ArialMT_Plain_10);
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
  Heltec.display->drawString(0, 0, "RGB");
  Heltec.display->drawString(0, 10, "CMYK");
  Heltec.display->display();  
}

void WiFiEvent(WiFiEvent_t event)
{
    Serial.printf("[WiFi-event] event: %d\n", event);
    switch(event)
    {
        case SYSTEM_EVENT_WIFI_READY:               /**< ESP32 WiFi ready */
            break;
        case SYSTEM_EVENT_SCAN_DONE:                /**< ESP32 finish scanning AP */
            break;

        case SYSTEM_EVENT_STA_START:                /**< ESP32 station start */
            break;
        case SYSTEM_EVENT_STA_STOP:                 /**< ESP32 station stop */
            break;

        case SYSTEM_EVENT_STA_CONNECTED:            /**< ESP32 station connected to AP */
            break;

        case SYSTEM_EVENT_STA_DISCONNECTED:         /**< ESP32 station disconnected from AP */
            break;

        case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:      /**< the auth mode of AP connected by ESP32 station changed */
            break;

        case SYSTEM_EVENT_STA_GOT_IP:               /**< ESP32 station got IP from connected AP */
        case SYSTEM_EVENT_STA_LOST_IP:              /**< ESP32 station lost IP and the IP is reset to 0 */
            break;

        case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:       /**< ESP32 station wps succeeds in enrollee mode */
        case SYSTEM_EVENT_STA_WPS_ER_FAILED:        /**< ESP32 station wps fails in enrollee mode */
        case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:       /**< ESP32 station wps timeout in enrollee mode */
        case SYSTEM_EVENT_STA_WPS_ER_PIN:           /**< ESP32 station wps pin code in enrollee mode */
            break;

        case SYSTEM_EVENT_AP_START:                 /**< ESP32 soft-AP start */
        case SYSTEM_EVENT_AP_STOP:                  /**< ESP32 soft-AP stop */
        case SYSTEM_EVENT_AP_STACONNECTED:          /**< a station connected to ESP32 soft-AP */
        case SYSTEM_EVENT_AP_STADISCONNECTED:       /**< a station disconnected from ESP32 soft-AP */
        case SYSTEM_EVENT_AP_PROBEREQRECVED:        /**< Receive probe request packet in soft-AP interface */
        case SYSTEM_EVENT_AP_STA_GOT_IP6:           /**< ESP32 station or ap interface v6IP addr is preferred */
        case SYSTEM_EVENT_AP_STAIPASSIGNED:
            break;

        case SYSTEM_EVENT_ETH_START:                /**< ESP32 ethernet start */
        case SYSTEM_EVENT_ETH_STOP:                 /**< ESP32 ethernet stop */
        case SYSTEM_EVENT_ETH_CONNECTED:            /**< ESP32 ethernet phy link up */
        case SYSTEM_EVENT_ETH_DISCONNECTED:         /**< ESP32 ethernet phy link down */
        case SYSTEM_EVENT_ETH_GOT_IP:               /**< ESP32 ethernet got IP from connected AP */
        case SYSTEM_EVENT_MAX:
            break;
    }
}

Ainda tem modificações a fazer para a relação com o próximo artigo (que já está 99% pronto, mas esse deveria precedê-lo). De qualquer modo, já é um bom exemplo. Nesse código do color picker com ESP32 temos:

  • Habilitação do sensor de cores TCS34725
  • Captura de cor RGB
  • Conversão de cor RGB para CMYK
  • Captura por acionamento do botão
  • Mensagem no display da Heltec Stick

Espero que acompanhe a série, porque a surpresa para o próximo artigo relacionado é incrível! Como dica do que está por vir, dê uma olhada no artigo do ILI9341 e tente imaginar qual será o projeto.

 

Revisão: Ricardo Amaral de Andrade

Djames Suhanko

Sobre o autor: Djames Suhanko é Perito Forense Digital. Já atuou com deployer em sistemas de missão critica em diversos países pelo mundão. Programador Shell, Python, C, C++ e Qt, tendo contato com embarcados ( ora profissionalmente, ora por lazer ) desde 2009.