Introdução 

Práticas como Integração Contínua (CI) e Entrega Contínua (CD) são pilares do desenvolvimento ágil e DevOps, e têm se tornado cada vez mais presentes no cotidiano dos desenvolvedores. Lançado em 2018, o GitHub Actions é uma ferramenta nativa do GitHub que simplifica a implementação de pipelines de CI/CD

A ferramenta permite criar fluxos de trabalho que executam ações automáticas, como compilar e testar cada pull request no repositório, garantindo a qualidade do código antes da integração. Além disso, é possível configurar pipelines para implantar alterações mescladas diretamente na produção, promovendo agilidade e confiabilidade no ciclo de desenvolvimento.

Essa prática é amplamente utilizada no desenvolvimento de software tradicional, como plataformas web. No entanto, o mundo dos sistemas embarcados, ainda enfrenta barreiras para adoção mais ampla dessa prática. Entre os principais desafios está a cultura da área, que historicamente prioriza processos manuais, testes locais e iterações mais controladas.

Apesar disso, o CI/CD vem se tornando cada vez mais útil para sistemas embarcados modernos, especialmente em projetos de IoT (Internet das Coisas). Soluções como o ESP-IDF, por exemplo, já oferecem suporte integrado para builds automatizados e testes, facilitando a integração com pipelines de CI/CD e promovendo maior agilidade e eficiência no desenvolvimento de sistemas embarcados.

Neste artigo, vamos desenvolver uma aplicação simples e integrar o GitHub Actions para configurar um pipeline de CI/CD. Esse pipeline será responsável por executar o build automático do firmware com três jobs principais: build, test e delivery, sempre que um pull request for aberto no repositório. O projeto será desenvolvido utilizando a Franzininho WiFi e o ESP-IDF, demonstrando como essas ferramentas podem ser usadas no desenvolvimento de sistemas embarcados.

Pré-requisitos

Antes de começar, certifique-se de ter os seguintes itens configurados:

  1. Conta no GitHub: Acesse o  GitHub e crie uma conta, se ainda não tiver.
  2. Git instalado no computador: Git
  3. ESP-IDF instalado no computador: Siga o guia oficial – Get Started – ESP32 – — ESP-IDF Programming Guide v5.4 documentation 

Hardware

Para este projeto, utilizarei a Franzininho WiFi Lab01, juntamente com o sensor DHT11 e o LED RGB integrados na placa.

GitHub Actions com Franzininho WiFi

Preparando o Ambiente 

Com o ESP-IDF devidamente instalado no sistema e adicionado como extensão no VSCode, siga os passos para criar um novo projeto:

  1. Abra o VSCode, aperte F1 e digite ESP-IDF: New Project
GitHub Actions com Franzininho WiFi
  1. Agora, nomeie o projeto e salve-o como quiser. Aqui, o nomeamos como “TEMPLIGHTFLOW”:
GitHub Actions com Franzininho WiFi
  1. Clique em “Choose Template”, selecione ESP-IDF e depois “hello_world”:
GitHub Actions com Franzininho WiFi
  1. Depois clique em “Create project using template hello_world” e na caixa de diálogo irá aparecer no canto inferior direito da janela do VSCode. Aperte Yes para abrir o projeto recém-criado em uma nova janela:
  1. Renomeie o nome do arquivo “hello_world_main.c” para “main.c”. Depois, entre no arquivo “CMakeList.txt” dentro da pasta main e troque “hello_world_main.c” para “main.c”.
  1. O upload na Franzininho Wifi usa o modo USB-CDC, portanto, abra o menuconfig e habilite esse modo: 

Em caso de dúvidas na preparação do Ambiente sugiro assistir o seguinte vídeo: 

ESP-IDF: Programe o #ESP32 com a Extensão oficial da Espressif para o VSCODE 

Leitura sensor DHT11 e controle LED RGB

Para a leitura do sensor DHT11 utilizaremos o componente dht disponível no ESP Component Registry. Acesse em: zorxx/dht • v1.0.1 • ESP Component Registry

  1. Abra o terminal e digite idf.py add-dependency “zorxx/dht^1.0.1” para incluir o componente: 
  1. No arquivo main.c, insira o seguinte código:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "dht.h" // Biblioteca para DHT11


// Configurações do DHT11
static const dht_sensor_type_t sensor_type = DHT_TYPE_DHT11;
static const gpio_num_t dht_gpio = 15;


// Definições dos LEDs RGB
#define LED_RED   14  
#define LED_GREEN 13        
#define LED_BLUE  12        


// Constantes para temperatura
#define TEMP_HIGH 300  // Limite para temperatura alta (30.0°C)
#define TEMP_LOW  200  // Limite para temperatura baixa (20.0°C)


// Tag para logs
static const char *TAG = "DHT_Example";


// Função para configurar os pinos dos LEDs
void configure_leds(void) {
    gpio_reset_pin(LED_RED);
    gpio_set_direction(LED_RED, GPIO_MODE_OUTPUT);


    gpio_reset_pin(LED_GREEN);
    gpio_set_direction(LED_GREEN, GPIO_MODE_OUTPUT);


    gpio_reset_pin(LED_BLUE);
    gpio_set_direction(LED_BLUE, GPIO_MODE_OUTPUT);
}


// Função para atualizar o estado dos LEDs com base na temperatura
void update_leds(int16_t temperature) {
    // Apaga todos os LEDs
    gpio_set_level(LED_RED, 0);
    gpio_set_level(LED_GREEN, 0);
    gpio_set_level(LED_BLUE, 0);


    // Acende o LED correspondente
    if (temperature > TEMP_HIGH) {
        gpio_set_level(LED_RED, 1);
    } else if (temperature > TEMP_LOW) {
        gpio_set_level(LED_GREEN, 1);
    } else {
        gpio_set_level(LED_BLUE, 1);
    }
}


// Função principal da tarefa
void dht_task(void *pvParameters) {
    int16_t temperature = 0;
    int16_t humidity = 0;


    configure_leds(); // Configura os pinos dos LEDs


    while (1) {
        // Tenta ler os dados do sensor
        if (dht_read_data(sensor_type, dht_gpio, &humidity, &temperature) == ESP_OK) {
            // Log dos dados lidos
            ESP_LOGI(TAG, "Umidade: %d.%d%%", humidity / 10, abs(humidity % 10));
            ESP_LOGI(TAG, "Temperatura: %d.%d°C", temperature / 10, abs(temperature % 10));


            // Atualiza os LEDs com base na temperatura
            update_leds(temperature);
        } else {
            ESP_LOGE(TAG, "Erro ao ler os dados do sensor");
        }


        vTaskDelay(pdMS_TO_TICKS(5000)); // Aguarda 5 segundos
    }
}


// Função principal
void app_main(void) {
    xTaskCreate(dht_task, "dht_task", configMINIMAL_STACK_SIZE * 3, NULL, 5, NULL);
}


  1. Compile o código e carregue-o na placa. O comportamento esperado é que o monitor exiba as leituras de temperatura e umidade, enquanto o LED altera sua cor conforme a temperatura registrada.
  1. Crie um arquivo chamado .gitignore no diretório principal do projeto e insira o seguinte conteúdo: 
/build/
/.vscode/
/.devcontainer/

Esta etapa é importante para evitar o envio de arquivos indesejados para o GitHub quando formos realizar o push do projeto em um repositório.

Criando repositório GitHub

Agora, vamos enviar este projeto para um repositório no GitHub. Siga os passos abaixo:

  1. Faça login na sua conta do GitHub. Crie um novo repositório. Aqui chamarei de TEMPLIGHTFLOW.
  1. Em seguida, vincule esse repositório ao diretório local do seu projeto. Para isso, entre na pasta do seu projeto, abra o terminal e execute os seguintes comandos:
git init
git remote add origin git@github.com:nomeusuariogithub/nomerepositorio.git
git branch -M main
git checkout -b main
git add .
git commit -m "Initial commit"
git push --set-upstream origin main

Lembrando que é preciso ter o git instalado no seu computador. 

Criando o Workflow para GitHub Actions

Agora, vamos configurar o workflow no GitHub Actions para automatizar o processo de CI/CD. Este workflow irá definir os passos necessários para compilar, testar e implantar seu projeto sempre que um pull request for aberto no repositório. Siga os seguintes passos: 

Criando um Token Pessoal no GitHub:

Para criar um token pessoal no GitHub, siga os passos abaixo:

  1. No GitHub, clique no ícone do seu perfil no canto superior direito e vá em Settings.
  2. Acesse Developer settings → Personal access tokens → Tokens (classic).
  3. Clique em Generate new token.
  4. Selecione as permissões repo e workflow para gerar o token.
  5. Defina um prazo para o token (a fins de teste usei 7 dias para este projeto).
  6. Após gerar o token, copie-o e guarde-o em um local seguro. Você precisará desse código (token) quando o GitHub solicitar uma senha para autenticação.

Criando um teste usando a biblioteca criterion:

1 – Dentro do seu diretório principal, crie uma pasta chamada test com um arquivo “test_dht.c“.

2 – No arquivo “test_dht.c” insira o seguinte código:

#include <criterion/criterion.h> // Inclui a biblioteca Criterion


// Função para verificar se a temperatura está dentro do intervalo razoável
int reasonable_temperature(int16_t temperature) {
    if (temperature > 200 && temperature < 300) { // Entre 20.0°C e 30.0°C
        return 1;
    } else {
        return 0;
    }
}


// Função para verificar se a umidade está dentro do intervalo razoável
int reasonable_humidity(int16_t humidity) {
    if (humidity >= 300 && humidity <= 800) { // Entre 30.0% e 80.0%
        return 1;
    } else {
        return 0;
    }
}


Test(sensor_values, test_temperature) {
    // Testa valores de temperatura
    cr_assert(reasonable_temperature(250) == 1, "Temperatura 25.0°C deveria ser considerada razoável");
}


Test(sensor_values, test_humidity) {
    // Testa valores de umidade
    cr_assert(reasonable_humidity(500) == 1, "Umidade 50.0%% deveria ser considerada razoável");
}

Esse código é um exemplo de testes unitários utilizando a biblioteca Criterion, que é uma ferramenta para testes de unidade em C. O objetivo desse código é testar duas funções: reasonable_temperature e reasonable_humidity, que verificam se os valores de temperatura e umidade estão dentro de intervalos razoáveis. 

Estamos utilizando essa biblioteca a fim de demonstrar o uso de GitHub Actions na automação de builds e testes básicos. Para testes mais realistas e adequados ao hardware do ESP32, você pode considerar usar frameworks como Unity.  Vamos explorar isso em um próximo artigo! 🙂

3 – Finalmente, vamos configurar o workflow:

a – No diretório principal do seu projeto, crie a estrutura de pastas .github/workflows e dentro dela crie o arquivo TempLightFlow.yml.

b – No arquivo yml vamos inserir o seguinte código:

name: TempLightFlow #nome workflow
on:
  pull_request:
    branches:
      - main # executa quando tiver uma pull request no repositório


jobs: # define os Jobs que serão executados no workflow
  build: # job compilação projeto
    runs-on: ubuntu-latest # usar última versão da distribuição Ubuntu
    permissions:
      contents: write # permitir escrita em conteúdos
    steps:
      - name: Repo checkout # nome usado para clonar o repositório
        uses: actions/checkout@v4 # action que clona o repositorio


      - name: esp-idf build # interação com o ESP-IDF
        uses: espressif/esp-idf-ci-action@v1 # action fornecida pela Espressif
        with:
          esp_idf_version: v5.4 # versão do ESP-IDF a ser utilizada
          target: esp32s2 # especifica a plataforma de compilação


      - name: Store Artifacts
        uses: actions/upload-artifact@v4 #Armazena os artefatos gerados pela compilação
        with:
          name: TempLightFlow # especifica o nome do artefato criado
          path: build/TempLightFlow.bin


  test: # job responsável por compilar os testes unitários e gerar os artefatos relacionados
    runs-on: ubuntu-latest
    permissions:
      checks: write
      pull-requests: write
    needs: [build] # job test só será executado se o Job build for bem-sucedido
    steps:
      - name: Repo checkout
        uses: actions/checkout@v4


      - name: Build tests
        run: |
          sudo apt-get install libcriterion-dev
          sudo apt-get install meson
          cd test
          gcc -o test test_dht.c -lcriterion
          ./test --xml > test.xml
     
      - name: Show results
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always() # sempre executa essa etapa
        with:
          files: test/test.xml #Arquivo com os resultados dos testes


  delivery: # job responsável pela entrega do artefato gerado
    runs-on: ubuntu-latest
    permissions:
      contents: write
    needs: [test] # este job depende do job "test" ser bem sucedido
    steps:
      - name: Repo checkout
        uses: actions/checkout@v4 # clona o repositório do GitHub


      - name: Download artifacts
        uses: actions/download-artifact@v4 # baixa os artefatos gerados no job "build"
        with:
          name: TempLightFlow


      - name: Create release
        uses: ncipollo/release-action@v1.13.0 # action para criar release
        with:
          artifacts: "TempLightFlow.bin" # artefato que será criado na release
          tag: 0.1.5 # tag do release

O workflow é chamado TempLightFlow e automatiza o processo de compilação, testes e entrega para um projeto baseado em ESP-IDF. Ele é acionado quando uma pull request é aberta na branch main e possui três jobs (tarefas), que seguem uma ordem lógica:

4 – Por fim, envie esse código para o repositório no GitHub usando os comandos:

git add .
git commit -m “workflow github actions”
git push origin main

Testando o Workflow do GitHub Actions com um Bug Intencional

Para simular um cenário de falha, vamos introduzir um bug intencional no arquivo test_dht.c. As modificações serão realizadas nas seguintes linhas:

Test(sensor_values, test_temperature) {
    // Testa valores de temperatura
    cr_assert(reasonable_temperature(400) == 1, "Temperatura nao deveria ser considerada razoavel");
}


Test(sensor_values, test_humidity) {
    // Testa valores de umidade
    cr_assert(reasonable_humidity(900) == 1, "Umidade nao deveria ser considerada razoavel");
}

Nos testes acima, estamos verificando se as funções reasonable_temperature e reasonable_humidity consideram os valores 400 (temperatura) e 900 (umidade) como razoáveis. No entanto, esses valores estão fora dos intervalos permitidos pelas funções, que retornam 0 (indicando valores não razoáveis). Como resultado, ambos os testes falharão.

Depois dessa modificação, crie uma nova branch chamada bug para isolar essas modificações. Abra o terminal e use os seguintes comandos:

git checkout -b bug
git add .
git commit -m "upadate Test"
git push origin bug

Após o push da branch, abra uma pull request no repositório para integrar a branch bug à branch principal. Isso acionará o workflow configurado no GitHub Actions, que executará as três tarefas: build, test e delivery. 

Para abrir um pull request, acesse o repositório no seu GitHub e vá até a aba “Pull Requests”, localizada no menu superior do repositório. Em seguida, clique no botão “New Pull Request” para iniciar o processo. Na tela de comparação, selecione a branch base (main) no lado esquerdo e a branch que contém as suas alterações (neste caso, bug) no lado direito.

Assim que o pull request for criado, o GitHub Actions será acionado automaticamente. Como introduzimos um bug intencional, você notará que o build será concluído com sucesso, mas os testes falharão. Como o processo de entrega (delivery) está configurado para depender do sucesso dos testes, ele não será realizado. O log gerado pelo workflow fornecerá detalhes sobre os erros detectados, permitindo identificar exatamente onde os problemas ocorreram.

Testando o Workflow do GitHub Actions com Bug Corrigido

Agora, vamos corrigir o bug introduzido no arquivo dht_test.c com as seguintes alterações:

Test(sensor_values, test_temperature) {
    // Testa valores de temperatura
    cr_assert(reasonable_temperature(280) == 1, "Temperatura eh considerada razoavel");
}


Test(sensor_values, test_humidity) {
    // Testa valores de umidade
    cr_assert(reasonable_humidity(500) == 1, "Umidade eh considerada razoavel");
}


Após aplicar essas correções, crie uma nova branch chamada bugfix para organizar a alteração. Siga os comandos abaixo para realizar o processo:

git checkout -b bugfix
git add .
git commit -m "test fixes"
git push origin bugfix

Em seguida, abra uma pull request no GitHub para integrar a branch bugfix com a branch base (main). Ao configurar a comparação, selecione a branch main no lado esquerdo e a branch bugfix no lado direito.

Dessa vez, ao abrir o pull request, o workflow será executado novamente. Como o bug foi corrigido, todos os testes deverão passar com sucesso, permitindo que o processo de entrega (delivery) seja concluído com êxito. No final, você verá a criação de uma nova release no repositório do GitHub, contendo o arquivo .bin compilado e disponível como um dos ativos (assets) da release. Este arquivo pode ser utilizado diretamente, por exemplo, para realizar uma atualização OTA (Over-the-Air) em dispositivos, facilitando a distribuição e implantação do código atualizado. Além disso, o repositório incluirá os pacotes de código-fonte nos formatos .zip e .tar.gz o acesso ao código correspondente à release.

GitHub Actions com Franzininho WiFi
GitHub Actions com Franzininho WiFi

Conclusão

Neste artigo, aprendemos como criar um processo completo de automação com GitHub Actions aplicado a um projeto com a Franzininho Wifi usando ESP-IDF. 

Essa abordagem permite maior eficiência, rastreabilidade e segurança no desenvolvimento de projetos embarcados, integrando práticas modernas de desenvolvimento e entrega contínua ao ambiente de sistemas embarcados. É um grande passo para aumentar a produtividade e a confiabilidade de projetos, mesmo em um contexto de hardware especializado como o ESP32.

Documentação

Graziele-Rodrigues/TEMPLIGHTFLOW

Documentação do GitHub Actions – GitHub Docs 

ESP-IDF Programming Guide – ESP32 – — ESP-IDF Programming Guide v5.4 documentation