Community project

Testador de Bonine - Compact Power Monitor Switch

Hugo Oliveira

Published July 16, 2026 · Updated July 16, 2026

ESP324 components6 assembly steps
Remix this project
Photo of Testador de Bonine - Compact Power Monitor Switch

The Testador de Bonine is a compact power monitor built around an ESP32 and Adafruit INA219 current sensor that diagnoses irrigation solenoid coils in the field. By measuring voltage and current through a test circuit, it identifies whether a coil is functioning normally, open-circuited, or short-circuited within seconds.

This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for connecting the INA219 sensor to the ESP32 via Grove connector, mounting the series resistor, and attaching the test probes. The included firmware implements real-time diagnostics with visual feedback on the display, allowing users to quickly validate solenoid health during installation or troubleshooting.

Wiring diagram

Interactive · read-only
Wiring diagram for Testador de Bonine - Compact Power Monitor Switch

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Parts list

Bill of materials
ComponentQtyNotes
Adafruit INA219 High-Side DC Current Sensor1INA219 high-side current and bus-voltage monitor breakout. It is powered from 3.3V or 5V and communicates over I2C. Route the measured load current through VIN+ and VIN-; those shunt terminals are part of the power path, not MCU GPIO.
Resistor100 Ω1Through-hole resistor (current-limiting in series with an LED)
Ponta de prova positiva (fio vermelho)1Fio/ponta de prova para ligar ao terminal positivo da bobine da eletroválvula
Ponta de prova negativa (fio preto)1Fio/ponta de prova para ligar ao terminal negativo da bobine da eletroválvula

Assembly

6 steps
  1. Materiais necessários

    Separa os seguintes componentes: M5StickS3, breakout Adafruit INA219, resistor 100Ω (castanho-preto-castanho), cabo Grove 4 pinos (HY2.0-4P), 2 fios com pinças jacaré ou pontas de prova (vermelho e preto).

    • Tip: O cabo Grove já tem os 4 pinos corretos — usa-o para ligar o INA219 ao M5StickS3
    • Tip: Qualquer breakout INA219 compatível com I2C e 3.3V serve
  2. Ligar INA219 ao M5StickS3 via Grove

    Liga o cabo Grove 4 pinos ao conector HY2.0-4P do M5StickS3 (na parte de baixo do dispositivo). O cabo tem 4 fios: preto=GND, vermelho=3V3, branco=G9(SDA), amarelo=G10(SCL). Liga o outro extremo ao INA219: GND→GND, VCC→VCC, SDA→SDA, SCL→SCL.

    • Tip: O conector Grove só entra num sentido — não forces
    • Tip: Verifica que os fios coincidem: preto-GND, vermelho-VCC, branco-SDA, amarelo-SCL
    • O INA219 é catalogado como módulo 5V mas funciona perfeitamente com 3.3V nesta aplicação — não ligues a 5V para evitar problemas de nível lógico
  3. Montar o resistor 100Ω em série

    O resistor 100Ω serve de proteção/limitador de corrente. Liga um extremo do resistor (P1) ao rail 3.3V do INA219 ou ao pino 3V3 do conector Grove. Liga o outro extremo (P2) ao pino VIN+ do INA219.

    • Tip: Podes usar uma mini breadboard para facilitar as ligações
    • Tip: O resistor 100Ω tem código de cores: castanho-preto-castanho-dourado
    • Não saltes este resistor — protege o M5StickS3 de um eventual curto-circuito na bobine
  4. Ligar as pontas de prova

    Liga um fio vermelho (ponta de prova positiva) ao pino VIN- do INA219. Liga um fio preto (ponta de prova negativa) ao GND. Estas serão as 2 pontas que tocam nos terminais da bobine da eletroválvula.

    • Tip: Usa pinças jacaré nas pontas para facilitar a ligação à bobine no campo
    • Tip: O comprimento do fio não é crítico — 30 a 50 cm é suficiente
    • Confirma a polaridade: vermelho=VIN-, preto=GND
  5. Verificar ligações antes de ligar

    Antes de ligar o M5StickS3 via USB-C, verifica: 1) INA219 ligado ao Grove corretamente, 2) Resistor 100Ω entre 3V3 e VIN+, 3) Fio vermelho em VIN-, 4) Fio preto no GND. O circuito de teste fica: 3.3V → resistor 100Ω → VIN+ → (shunt INA219) → VIN- → fio vermelho → bobine → fio preto → GND.

    • Tip: Fotografa as ligações para referência futura
  6. Usar o testador no campo

    Liga o M5StickS3 via USB-C (ou usa a bateria interna de 250mAh). O ecrã mostra 'TESTADOR BOBINE - Prima o botão A'. Encosta o fio vermelho e o fio preto aos 2 terminais da bobine da eletroválvula (sem polaridade definida nas bobines AC/resistivas). Carrega no Botão A (lateral do M5StickS3) e aguarda 2 segundos.

    • Tip: Nas bobines resistivas não há polaridade — podes inverter os fios
    • Tip: Para um resultado mais fiável, testa com a bobine desligada do sistema de rega
    • Tip: Resultado VERDE = bobine boa. Resultado VERMELHO = bobine partida/aberta. Resultado LARANJA = curto-circuito.
    • Não toques nos terminais metálicos das pontas de prova durante o teste
    • Não uses este testador em bobines que ainda estejam ligadas a 12V/24V — desliga sempre a alimentação do sistema de rega primeiro

Firmware

ESP32
firmware.cppDeploy to device
#include <Arduino.h>
#include <M5Unified.h>
#include <Wire.h>
#include <Adafruit_INA219.h>

// I2C externo no Grove (G9=SDA, G10=SCL)
#define SDA_PIN 9
#define SCL_PIN 10

// Limiares de diagnóstico para bobines de rega típicas (via 3.3V + 100Ω)
// Bobine boa: resistência 20–80Ω → corrente ~14–110mA com 3.3V + 100Ω série
#define CURRENT_MIN_MA   5.0f    // Abaixo: circuito aberto / bobine queimada
#define CURRENT_MAX_MA  120.0f   // Acima: curto-circuito
#define VOLTAGE_MIN_V    0.5f    // Tensão mínima esperada no shunt


// Hoisted type definitions
enum State { IDLE, TESTING, RESULT_OK, RESULT_OPEN, RESULT_SHORT };


// Forward declarations
void drawIdle();
void drawTesting(float elapsed);
void drawResult(State s);

Adafruit_INA219 ina219;


State state = IDLE;

float measuredCurrentMA = 0;
float measuredVoltageV  = 0;
float measuredPowerMW   = 0;
float measuredResistance = 0;

unsigned long testStart = 0;
const unsigned long TEST_DURATION = 2000; // 2 segundos de leitura média

// Acumuladores para média
float sumCurrent = 0;
float sumVoltage = 0;
int   sampleCount = 0;

void drawIdle() {
  M5.Display.fillScreen(0x1A3A); // Azul escuro
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.setTextSize(1);
  M5.Display.setCursor(10, 10);
  M5.Display.setTextSize(2);
  M5.Display.println("TESTADOR");
  M5.Display.println("BOBINE");
  M5.Display.setTextSize(1);
  M5.Display.println("");
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.println("Ligue os 2 fios");
  M5.Display.println("a bobine e prima");
  M5.Display.println("o botao A");
  M5.Display.println("");
  M5.Display.setTextColor(TFT_YELLOW);
  M5.Display.println("  [A] Iniciar");
}

void drawTesting(float elapsed) {
  M5.Display.fillScreen(0x2820); // Verde escuro escuro
  M5.Display.setTextColor(TFT_YELLOW);
  M5.Display.setTextSize(2);
  M5.Display.setCursor(5, 5);
  M5.Display.println("A MEDIR...");
  M5.Display.setTextSize(1);
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.println("");
  M5.Display.print("Corrente: ");
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.print(measuredCurrentMA, 1);
  M5.Display.println(" mA");
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.print("Tensao:   ");
  M5.Display.setTextColor(TFT_CYAN);
  M5.Display.print(measuredVoltageV, 2);
  M5.Display.println(" V");
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.print("Resist:   ");
  M5.Display.setTextColor(TFT_CYAN);
  if (measuredCurrentMA > 0.5f) {
    M5.Display.print(measuredResistance, 0);
    M5.Display.println(" Ohm");
  } else {
    M5.Display.println("--- Ohm");
  }
  // Barra de progresso
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.println("");
  int barWidth = (int)((elapsed / TEST_DURATION) * 110);
  if (barWidth > 110) barWidth = 110;
  M5.Display.drawRect(10, 175, 110, 12, TFT_WHITE);
  M5.Display.fillRect(11, 176, barWidth, 10, TFT_GREEN);
  M5.Display.setTextColor(TFT_DARKGREY);
  M5.Display.setCursor(10, 192);
  M5.Display.println("[A] Cancelar");
}

void drawResult(State s) {
  if (s == RESULT_OK) {
    M5.Display.fillScreen(0x0440); // Verde escuro
    M5.Display.setTextColor(TFT_GREEN);
    M5.Display.setTextSize(2);
    M5.Display.setCursor(15, 8);
    M5.Display.println("BOBINE");
    M5.Display.println(" OK!");
  } else if (s == RESULT_OPEN) {
    M5.Display.fillScreen(0x4000); // Vermelho escuro
    M5.Display.setTextColor(TFT_RED);
    M5.Display.setTextSize(2);
    M5.Display.setCursor(5, 8);
    M5.Display.println("QUEIMADA");
    M5.Display.println("/ABERTA");
  } else if (s == RESULT_SHORT) {
    M5.Display.fillScreen(0x4200); // Laranja escuro
    M5.Display.setTextColor(TFT_ORANGE);
    M5.Display.setTextSize(2);
    M5.Display.setCursor(10, 8);
    M5.Display.println("CURTO-");
    M5.Display.println("CIRCUITO");
  }
  M5.Display.setTextSize(1);
  M5.Display.setTextColor(TFT_WHITE);
  M5.Display.setCursor(5, 80);
  M5.Display.print("Corrente: ");
  M5.Display.print(measuredCurrentMA, 1);
  M5.Display.println(" mA");
  M5.Display.print("Tensao:   ");
  M5.Display.print(measuredVoltageV, 2);
  M5.Display.println(" V");
  M5.Display.print("Resist:   ");
  if (measuredCurrentMA > 0.5f) {
    M5.Display.print(measuredResistance, 0);
    M5.Display.println(" Ohm");
  } else {
    M5.Display.println("---  Ohm");
  }
  if (s == RESULT_OPEN) {
    M5.Display.setTextColor(TFT_YELLOW);
    M5.Display.println("");
    M5.Display.println("Substituir bobine");
  } else if (s == RESULT_SHORT) {
    M5.Display.setTextColor(TFT_YELLOW);
    M5.Display.println("");
    M5.Display.println("Substituir bobine");
  }
  M5.Display.setTextColor(TFT_DARKGREY);
  M5.Display.setCursor(5, 200);
  M5.Display.println("[A] Novo teste");
}

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.Display.setRotation(0);
  M5.Display.setBrightness(200);

  Wire.begin(SDA_PIN, SCL_PIN);

  if (!ina219.begin(&Wire)) {
    M5.Display.fillScreen(TFT_RED);
    M5.Display.setTextColor(TFT_WHITE);
    M5.Display.setTextSize(1);
    M5.Display.setCursor(5, 80);
    M5.Display.println("ERRO: INA219");
    M5.Display.println("nao encontrado!");
    M5.Display.println("Verifica ligacoes");
    while (1) delay(100);
  }

  // Calibração para leitura até 400mA (sobra margem para curtos)
  ina219.setCalibration_16V_400mA();

  drawIdle();
}

void loop() {
  M5.update();

  if (state == IDLE) {
    if (M5.BtnA.wasPressed()) {
      state = TESTING;
      testStart = millis();
      sumCurrent = 0;
      sumVoltage = 0;
      sampleCount = 0;
      measuredCurrentMA = 0;
      measuredVoltageV = 0;
    }
  }
  else if (state == TESTING) {
    if (M5.BtnA.wasPressed()) {
      // Cancelar
      state = IDLE;
      drawIdle();
      return;
    }

    unsigned long elapsed = millis() - testStart;

    // Leitura contínua
    float current_mA = ina219.getCurrent_mA();
    float bus_V      = ina219.getBusVoltage_V();
    float shunt_V    = ina219.getShuntVoltage_mV() / 1000.0f;

    // Filtra leituras negativas (ruído)
    if (current_mA < 0) current_mA = 0;

    measuredCurrentMA = current_mA;
    measuredVoltageV  = bus_V;
    // Resistência = V shunt / I (estimativa da bobine, excluindo o 100Ω série)
    // V total = 3.3V, V no 100Ω = current_mA/1000 * 100, V na bobine = resto
    float v_serie = (current_mA / 1000.0f) * 100.0f;
    float v_bobine = 3.3f - v_serie - shunt_V;
    if (current_mA > 0.5f) {
      measuredResistance = (v_bobine / (current_mA / 1000.0f));
      if (measuredResistance < 0) measuredResistance = 0;
    } else {
      measuredResistance = 9999;
    }

    sumCurrent += current_mA;
    sumVoltage += bus_V;
    sampleCount++;

    drawTesting((float)elapsed);

    if (elapsed >= TEST_DURATION) {
      // Calcula médias
      measuredCurrentMA = sumCurrent / sampleCount;
      measuredVoltageV  = sumVoltage / sampleCount;
      float v_s = (measuredCurrentMA / 1000.0f) * 100.0f;
      float v_b = 3.3f - v_s;
      if (measuredCurrentMA > 0.5f) {
        measuredResistance = v_b / (measuredCurrentMA / 1000.0f);
        if (measuredResistance < 0) measuredResistance = 0;
      } else {
        measuredResistance = 9999;
      }

      // Diagnóstico
      if (measuredCurrentMA < CURRENT_MIN_MA) {
        state = RESULT_OPEN;
      } else if (measuredCurrentMA > CURRENT_MAX_MA) {
        state = RESULT_SHORT;
      } else {
        state = RESULT_OK;
      }
      drawResult(state);
    }

    delay(100);
  }
  else if (state == RESULT_OK || state == RESULT_OPEN || state == RESULT_SHORT) {
    if (M5.BtnA.wasPressed()) {
      state = IDLE;
      drawIdle();
    }
  }
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik