25 de outubro de 2021

Do bit Ao Byte

Embarcados, Linux e programação

Color picker com ESP32 – Heltec Stick

Color picker com ESP32

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