Які часи такі і поробки! В зв'язку з відсутністю опалення і світла через москальські обстріли нашої енергетики, мої батьки вимушені були придбати газовий обігрівач, щоб хоч якось зігрітися цієї зими. Тож я вирішив зробити декілька пристроїв: це детектор чадного газу (CO) та сенсор витоку газу, про останній сьогодні ми і поговоримо.

Тож нижче ви знайдете перелік всіх використаних компонентів та детальну схему підключення.

Список компонентів:

Схема підключення

assembly scheme

Програмний код

main.cpp for PlatformIO
    
#include 
#include 
#include 
#include 
#include "esp_bt.h"

// --- PINS (ESP32-C3 Super Mini) ---
#define PIN_MQ6_ANALOG  3   
#define PIN_BUZZER      5   
#define PIN_LED_RED     6   
#define PIN_MUTE_BTN    10  

// I2C Pins
#define OLED_SDA 8
#define OLED_SCL 9

// --- SETTINGS ---
#define GAS_THRESHOLD   1500  
#define SCREEN_TIMEOUT  30000 // 30 Seconds until sleep
#define BLINK_SPEED     500   
#define BUZZER_FREQ     4000  // Your tuned frequency

// --- DISPLAY DRIVER ---
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C display(U8G2_R0, U8X8_PIN_NONE);

// --- VARIABLES ---
int valGas = 0;
bool isMuted = false;
bool alarmActive = false;
bool isScreenOn = true; 
unsigned long lastInteraction = 0; // Tracks last button press or alarm
unsigned long lastBlinkTime = 0;
bool ledState = LOW;

void setup() {
  // 1. Power Saving
  WiFi.mode(WIFI_OFF);
  btStop();
  setCpuFrequencyMhz(80);

  // 2. Pins
  pinMode(PIN_LED_RED, OUTPUT);
  pinMode(PIN_BUZZER, OUTPUT);
  pinMode(PIN_MQ6_ANALOG, INPUT);
  pinMode(PIN_MUTE_BTN, INPUT_PULLUP);
  digitalWrite(PIN_LED_RED, LOW);

  // 3. I2C Force
  Wire.begin(OLED_SDA, OLED_SCL);

  // 4. Display Start
  display.setI2CAddress(0x3C * 2);
  display.begin();
  display.setContrast(255);
  
  // Warmup
  display.clearBuffer();
  display.setFont(u8g2_font_helvB10_tr); 
  display.drawStr(5, 20, "Sensor Warmup");
  display.sendBuffer();
  
  delay(1000); 
  lastInteraction = millis(); // Reset timer
}

// --- LOGIC ---

void handleLed(bool isDanger) {
  if (isDanger) {
    if (millis() - lastBlinkTime >= BLINK_SPEED) {
      lastBlinkTime = millis();
      ledState = !ledState;
      digitalWrite(PIN_LED_RED, ledState);
    }
  } else {
    digitalWrite(PIN_LED_RED, LOW);
    ledState = LOW;
  }
}

void managePowerAndMute(bool isDanger) {
  // 1. Check Button
  if (digitalRead(PIN_MUTE_BTN) == LOW) {
    lastInteraction = millis(); // Reset sleep timer
    
    // If we are in danger, this button mutes the sound
    if (isDanger) {
        isMuted = true;
    }
    
    // If screen was off, just waking it up is enough (debounce)
    delay(200); 
  }

  // 2. Decide: Sleep or Wake?
  // Stay Awake if: Danger OR (Time since last interaction < 30s)
  bool shouldBeOn = isDanger || (millis() - lastInteraction < SCREEN_TIMEOUT);

  if (shouldBeOn) {
    if (!isScreenOn) {
      display.setPowerSave(0); // Wake up
      isScreenOn = true;
    }
  } else {
    if (isScreenOn) {
      display.setPowerSave(1); // Sleep (Screen goes black)
      isScreenOn = false;
    }
  }
}

void handleSound(bool isDanger) {
  if (!isDanger || isMuted) {
    ledcDetachPin(PIN_BUZZER);
    pinMode(PIN_BUZZER, OUTPUT);
    digitalWrite(PIN_BUZZER, LOW);
    return;
  }
  
  // Pulsing Alarm
  if ((millis() / 500) % 2 == 0) {
    if (!ledcRead(0)) { 
        ledcSetup(0, BUZZER_FREQ, 8);
        ledcAttachPin(PIN_BUZZER, 0);
    }
    ledcWriteTone(0, BUZZER_FREQ);
  } else {
    ledcWriteTone(0, 0);
  }
}

void drawScreen(int gasValue, bool isDanger) {
  if (!isScreenOn) return;

  display.clearBuffer();

  if (isDanger) {
    // --- DANGER UI ---
    display.setFont(u8g2_font_helvB10_te); 
    display.drawStr(25, 12, "! DANGER !");
    
    display.setFont(u8g2_font_logisoso16_tf);
    display.setCursor(45, 32);
    display.print(gasValue);
    
    display.drawFrame(0, 0, 128, 32);
  } 
  else {
    // --- SAFE UI (SIDE-BY-SIDE) ---
    
    // 1. The Label (Small, on the Left)
    // Centered vertically around the middle
    display.setFont(u8g2_font_helvB10_tf);
    display.drawStr(0, 20, "GAS");
    
    // 2. The Value (HUGE, on the Right)
    // We use your preferred Size 24 font
    display.setFont(u8g2_font_logisoso24_tf); 
    
    // Adjust X (35) based on how wide your number is
    display.setCursor(35, 32); 
    display.print(gasValue);
    
    // 3. The Checkmark (Far Right)
    // We make it a bit smaller or move it to fit
    display.setFont(u8g2_font_open_iconic_check_2x_t);
    display.drawGlyph(110, 24, 64); 
  }

  if (isMuted && isDanger) {
    display.setFont(u8g2_font_open_iconic_embedded_1x_t);
    display.drawGlyph(0, 32, 74); 
  }

  display.sendBuffer();
}

void loop() {
  valGas = analogRead(PIN_MQ6_ANALOG);
  
  if (valGas > GAS_THRESHOLD) {
    alarmActive = true;
  } else {
    alarmActive = false;
    isMuted = false; 
  }

  managePowerAndMute(alarmActive);
  handleLed(alarmActive);
  handleSound(alarmActive);
  drawScreen(valGas, alarmActive);
  
  delay(50);
}
    
  

Відео на youtube

Підтримка