Gas Leakage Detector from Old Solar Light

Hello people in this project i will show you how to make Gas Leakage Detector from old solar light.
This solar light was abandoned and i wanted to make something out of this which would be helpful.
Solar light was upcycled for its hardware components and i will be adding the circuit for gas leakage detection.
This gas leakage detector works in real time, OLED shows the live status.
If any leakage occurs the LED starts to show graph patterns and the buzzer beeps along with flashes of light from led filament.
You may also like my previous project on wet and dry waste segregation system.
Materials for gas leakage detector
- ESP32 C3 Super Mini
- Jumper Cables
- Gas Sensor
- OLED Module
- Buzzer
- 3D Printed part( any alternative with cardboard will also work)
- Arduino IDE
I will start by removing all the internal components from old solar lamp and then washing them.
I will use bowl of water and liquid soap to clean and a brush to reach the tricky places.
Gas Leakage Detector Circuit Diagram
This is the circuit diagram which we will be using to make this project and it simple

I will give detailed explanation on the circuit diagram
Connect all the ground terminals to one common wire and that will go to gnd on the esp32 board.
OLED module positive pin to 3v on esp32 and the Sda to pin 1 and the Scl to pin 0 on the esp32.
Gas sensor positive to 3v pin and the A0 pin is connected pin 3 to and the D0 pin is connected to pin 4.
Buzzer positive side to Pin 5 and the edison filament positive to pin 2.
After you make all the wirings this is how the final circuit looks like.

Still want to simplify this wire circuit?
Get a custom PCB from JLCPCB they have PCB services for any type of projects, Use their EasyEDA tool to design PCB.

Im satisfied with their services and also they offer fast shipping, First time user ? Visit them to avail great coupons and offers.
Simply visit their site to explore all their services and choose the one you need.

Arduino Code for Gas Leakage Detector
This is the program that we will be using to make this project
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ═══════════════════════════════════════════
// PIN CONFIGURATION
// ═══════════════════════════════════════════
#define SDA_PIN 1
#define SCL_PIN 0
#define LED_PIN 2
#define GAS_AO 3
#define GAS_DO 4
#define BUZ_PIN 5
// ═══════════════════════════════════════════
// DISPLAY CONFIGURATION
// ═══════════════════════════════════════════
#define OLED_W 128
#define OLED_H 32
#define OLED_ADDR 0x3C
Adafruit_SSD1306 oled(OLED_W, OLED_H, &Wire, -1);
// PWM Configuration
#define LED_CH 1
#define LED_FREQ 20000
#define LED_RES 8
// Gas State Machine
enum GasState { GAS_SAFE, GAS_WARN, GAS_DANGER };
GasState gasState = GAS_SAFE;
// Sensor Filtering Variables
#define SENSOR_SAMPLES 10
int adcBuffer[SENSOR_SAMPLES];
uint8_t bufferIdx = 0;
int baselineADC = 0;
int gasPpmLevel = 0;
unsigned long lastSensorMs = 0;
// System Timers
unsigned long buzMs = 0;
bool buzOn = false;
unsigned long frameMs = 0;
#define FRAME_MS 30 // ~33 FPS for smooth ECG sweep
// ECG Graph Generator Variables
#define WAVE_W 128
int16_t waveBuf[WAVE_W];
float ecgPos = 0.0f;
float ledPhase = 0.0f;
void setLED(uint8_t brightness) {
ledcWrite(LED_CH, brightness);
}
// ═══════════════════════════════════════════
// SENSOR FILTERING & NOISE ELIMINATION
// ═══════════════════════════════════════════
int getMedianADC() {
int temp[SENSOR_SAMPLES];
for (int i = 0; i < SENSOR_SAMPLES; i++) temp[i] = adcBuffer[i];
// Insertion Sort
for (int i = 1; i < SENSOR_SAMPLES; i++) {
int key = temp[i];
int j = i - 1;
while (j >= 0 && temp[j] > key) {
temp[j + 1] = temp[j];
j--;
}
temp[j + 1] = key;
}
return temp[SENSOR_SAMPLES / 2];
}
void readSensor() {
if (millis() - lastSensorMs < 100) return;
lastSensorMs = millis();
// Populate ring buffer
adcBuffer[bufferIdx] = analogRead(GAS_AO);
bufferIdx = (bufferIdx + 1) % SENSOR_SAMPLES;
int currentADC = getMedianADC();
bool digitalTrip = (digitalRead(GAS_DO) == LOW); // LOW indicates module threshold crossed
// Measure deviation from warmup baseline
int drop = baselineADC - currentADC;
if (drop < 0) drop = 0;
gasPpmLevel = map(drop, 0, 1500, 0, 100);
gasPpmLevel = constrain(gasPpmLevel, 0, 100);
// Trigger states primarily on digital pin + significant drop
if (digitalTrip && drop > 600) {
gasState = GAS_DANGER;
} else if (digitalTrip || drop > 250) {
gasState = GAS_WARN;
} else {
gasState = GAS_SAFE;
}
}
// ═══════════════════════════════════════════
// LED & ALARM CONTROL
// ═══════════════════════════════════════════
void handleLED() {
if (gasState == GAS_DANGER) {
bool on = (millis() / 100) % 2;
setLED(on ? 255 : 0);
return;
}
if (gasState == GAS_WARN) {
ledPhase += 0.1f;
if (ledPhase > TWO_PI) ledPhase -= TWO_PI;
float b = (sin(ledPhase) + 1.0f) / 2.0f;
setLED((uint8_t)(b * 255));
return;
}
setLED(0); // Strictly off in safe state
}
void handleBuzzer() {
if (gasState == GAS_SAFE) {
digitalWrite(BUZ_PIN, LOW);
buzOn = false;
return;
}
unsigned long interval = (gasState == GAS_DANGER) ? 100 : 600;
if (millis() - buzMs > interval) {
buzMs = millis();
buzOn = !buzOn;
digitalWrite(BUZ_PIN, buzOn ? HIGH : LOW);
}
}
// ═══════════════════════════════════════════
// REALISTIC ECG GRAPH GENERATOR
// ═══════════════════════════════════════════
float getECGSample(float pos) {
float p = fmod(pos, 1.0f);
// Standby Smooth Medical ECG Trace
if (gasState == GAS_SAFE) {
if (p < 0.10f) return 0.0f;
if (p < 0.18f) return -1.5f * sin((p - 0.10f) / 0.08f * PI); // P-Wave
if (p < 0.25f) return 0.0f;
if (p < 0.28f) return 2.0f; // Q-Dip
if (p < 0.33f) return -12.0f * sin((p - 0.28f) / 0.05f * PI); // R-Spike
if (p < 0.37f) return 4.0f; // S-Dip
if (p < 0.45f) return 0.0f;
if (p < 0.58f) return -2.5f * sin((p - 0.45f) / 0.13f * PI); // T-Wave
return 0.0f;
}
// Gas Alert High Voltage Distortion Pattern
float noise = (random(-20, 20) / 10.0f);
if (gasState == GAS_WARN) {
return (-8.0f * sin(p * TWO_PI * 3.0f)) + noise;
} else {
return (-14.0f * sin(p * TWO_PI * 5.0f)) + (noise * 2.0f); // Chaotic Spikes
}
}
void updateWave() {
float speed = (gasState == GAS_SAFE) ? 0.022f : 0.045f;
ecgPos += speed;
if (ecgPos > 1.0f) ecgPos -= 1.0f;
for (int x = 0; x < WAVE_W - 1; x++) {
waveBuf[x] = waveBuf[x + 1];
}
int cy = OLED_H / 2 + 3;
int newY = cy + (int)getECGSample(ecgPos);
newY = constrain(newY, 11, OLED_H - 1);
waveBuf[WAVE_W - 1] = newY;
}
// ═══════════════════════════════════════════
// DISPLAY RENDERING ROUTINES
// ═══════════════════════════════════════════
void drawStatusBar() {
oled.fillRect(0, 0, OLED_W, 10, SSD1306_BLACK);
oled.setTextSize(1);
oled.setTextColor(SSD1306_WHITE);
if (gasState == GAS_DANGER) {
if ((millis() / 200) % 2) {
oled.setCursor(16, 1);
oled.print("!! LEAK DETECTED !!");
}
} else if (gasState == GAS_WARN) {
oled.setCursor(0, 1);
oled.print("! GAS WARN");
oled.setCursor(88, 1);
oled.print(gasPpmLevel);
oled.print("%");
} else {
// Standby Mode: ECG Heartbeat Display
oled.setCursor(0, 1);
oled.print("ECG:NORMAL");
// Live Uptime Display
unsigned long sec = millis() / 1000;
char timeStr[10];
sprintf(timeStr, "%02lu:%02lu", (sec / 60) % 60, sec % 60);
oled.setCursor(92, 1);
oled.print(timeStr);
}
oled.drawFastHLine(0, 10, OLED_W, SSD1306_WHITE);
}
void drawWave() {
oled.fillRect(0, 11, OLED_W, OLED_H - 11, SSD1306_BLACK);
// Background Grid Line
for (int x = 0; x < OLED_W; x += 6) {
oled.drawPixel(x, OLED_H / 2 + 3, SSD1306_WHITE);
}
// Draw ECG waveform
for (int x = 1; x < WAVE_W; x++) {
oled.drawLine(x - 1, waveBuf[x - 1], x, waveBuf[x], SSD1306_WHITE);
}
}
void warmup() {
unsigned long start = millis();
#define WU_DUR 15000
long sum = 0;
int count = 0;
while (millis() - start < WU_DUR) {
float prog = (float)(millis() - start) / WU_DUR;
int barW = (int)(prog * (OLED_W - 4));
sum += analogRead(GAS_AO);
count++;
oled.clearDisplay();
oled.setTextSize(1);
oled.setTextColor(SSD1306_WHITE);
oled.setCursor(20, 2);
oled.print("CALIBRATING...");
oled.drawRect(2, 14, OLED_W - 4, 8, SSD1306_WHITE);
if (barW > 0) oled.fillRect(3, 15, barW, 6, SSD1306_WHITE);
char buf[8];
sprintf(buf, "%d%%", (int)(prog * 100));
oled.setCursor((OLED_W - strlen(buf) * 6) / 2, 24);
oled.print(buf);
oled.display();
delay(50);
yield();
}
baselineADC = sum / count; // Store baseline clean-air reading
// Pre-fill noise buffer
for (int i = 0; i < SENSOR_SAMPLES; i++) {
adcBuffer[i] = baselineADC;
}
}
// ═══════════════════════════════════════════
// SETUP & MAIN LOOP
// ═══════════════════════════════════════════
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
pinMode(GAS_AO, INPUT);
pinMode(GAS_DO, INPUT_PULLUP);
pinMode(BUZ_PIN, OUTPUT);
digitalWrite(BUZ_PIN, LOW);
analogSetAttenuation(ADC_11db);
ledcSetup(LED_CH, LED_FREQ, LED_RES);
ledcAttachPin(LED_PIN, LED_CH);
setLED(0);
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(400000);
if (!oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
for (;;);
}
oled.clearDisplay();
oled.display();
for (int i = 0; i < WAVE_W; i++) {
waveBuf[i] = OLED_H / 2 + 3;
}
warmup();
}
void loop() {
yield();
readSensor();
handleBuzzer();
if (millis() - frameMs < FRAME_MS) return;
frameMs = millis();
handleLED();
updateWave();
oled.clearDisplay();
drawStatusBar();
drawWave();
oled.display();
}
Open IDE, use this program and you are good to go.
Make sure to select proper port type and the board type(esp32 c3 super mini dev module)
After the program is done uploading you can test the circuit for its working.
How to Use
Using this is very much fun and exciting.
Start by connecting the esp32 board to usb power supply and wait for the sensor to calibrate.
You can see all these statuses on the OLED module.
Expose the gas sensor with nail polish remover or the gas from small lighter.

The flashes of light and the buzzer and also the OLED shows the leakage statuses.
This was all about this project, consider checking this video tutorial of this project.



