पार्ट्स लिस्ट
| पार्ट | संख्या | लगभग | खरीदें |
|---|---|---|---|
| Arduino Uno R3 (CH340 clone) | 1 | ₹450 | खरीदें Arduino Uno R3 (CH340 clone) |
| MQ-6 LPG gas sensor module | 1 | ₹160 | खरीदें MQ-6 LPG gas sensor module |
| Active buzzer module (या transistor के ज़रिए 5 V सायरन) | 1 | ₹25 | खरीदें Active buzzer module (या transistor के ज़रिए 5 V सायरन) |
| SIM800L GSM module + SIM | 1 | ₹350 | खरीदें SIM800L GSM module + SIM |
| LM2596 buck module set to 4.0 V + 1000 µF capacitor | 1 | ₹100 | खरीदें LM2596 buck module set to 4.0 V + 1000 µF capacitor |
| Resistors 10 kΩ + 20 kΩ (RX divider) | 1 | ₹5 | खरीदें Resistors 10 kΩ + 20 kΩ (RX divider) |
| 9–12 V 2 A adapter | 1 | ₹200 | खरीदें 9–12 V 2 A adapter |
| कुल | ₹1,290 |
एफ़िलिएट सूचना: “खरीदें” लिंक से FlashReady को बिना अतिरिक्त लागत के छोटा कमीशन मिल सकता है। कीमतें अनुमानित हैं।
वायरिंग
| Arduino Uno R3 | Part · pin | Wire | Notes |
|---|---|---|---|
| A0 | MQ-6 gas sensor module · AO | green | |
| 5V | MQ-6 gas sensor module · VCC | red | हीटर लगभग 150 mA लेता है |
| GND | MQ-6 gas sensor module · GND | black | |
| D8 | Active buzzer module (high-level trigger) · I/O | orange | |
| 5V | Active buzzer module (high-level trigger) · VCC | red | |
| GND | Active buzzer module (high-level trigger) · GND | black | |
| D2 | SIM800L GSM/GPRS module (2G) · TXD | blue | |
| D3 | SIM800L GSM/GPRS module (2G) · RXD | yellow | Via 10 kΩ / 20 kΩ divider — SIM800L के 2.8 V वाले serial इनपुट को बचाता है |
| LM2596 buck, 4.0 V पर सेट · + | SIM800L GSM/GPRS module (2G) · VCC | red | 9–12 V अडैप्टर से चलने वाला 4.0 V / 2 A buck; मॉड्यूल पर VCC/GND के बीच 1000 µF |
| LM2596 buck, 4.0 V पर सेट · - | SIM800L GSM/GPRS module (2G) · GND | black | |
| GND | SIM800L GSM/GPRS module (2G) · GND | black | कॉमन ग्राउंड: Uno का GND और buck का − दोनों SIM800L के GND पर |
बोर्ड की ख़ास बातें: LPG हवा से भारी होती है: MQ-6 को रसोई की दीवार पर नीचे, फ़र्श से लगभग 30 cm ऊपर लगाएँ — चूल्हे की सीधी गर्मी और भाप से दूर। MQ-6 की रीडिंग स्थिर होने में कुछ मिनट की हीटिंग लगती है (और बिल्कुल पहली बार burn-in में लगभग 24 घंटे)। स्केच पावर-ऑन के बाद पहले 3 मिनट की रीडिंग अनदेखी करता है। ALARM_LEVEL एक raw नंबर है, ppm नहीं। वार्म-अप के बाद साफ़ हवा में वैल्यू पढ़ें और थ्रेशोल्ड उससे काफ़ी ऊपर रखें — फिर लाइटर की गैस (बिना जलाए) से, कुछ दूरी पर रखकर टेस्ट करें। यह शौक़िया (hobby) अलार्म है, सर्टिफ़ाइड गैस डिटेक्टर नहीं। गैस की गंध आए तो: कोई स्विच न दबाएँ, कोई आग न जलाएँ, रेगुलेटर बंद करें, खिड़कियाँ खोल दें।
कोड
// LPG gas leak alarm with SMS — Arduino Uno R3 + MQ-6 + buzzer module + SIM800L
// MQ-6 analog output on A0, buzzer module on D8, SIM800L on D2/D3 (RXD through a divider).
// The sensor needs a few minutes of heating before readings mean anything.
#include <SoftwareSerial.h>
#include "fr_settings.h"
constexpr uint8_t GAS_PIN = A0;
constexpr uint8_t BUZZER_PIN = 8; // buzzer module I/O (high = on)
constexpr uint8_t SIM_RX = 2; // <- SIM800L TXD
constexpr uint8_t SIM_TX = 3; // -> divider -> SIM800L RXD
constexpr int ALARM_LEVEL = 400; // raw ADC reading; set it with the calibration steps
constexpr unsigned long WARM_UP_MS = 3UL * 60UL * 1000UL;
constexpr unsigned long SMS_GAP_MS = 10UL * 60UL * 1000UL; // at most one SMS per 10 minutes
const char* ALERT_TO = "+91XXXXXXXXXX";
// Filled in from the FlashReady flasher over USB (kept in EEPROM), or type the values above.
FrSetting SETTINGS[] = {{"alert_to", &ALERT_TO, true}};
SoftwareSerial sim(SIM_RX, SIM_TX);
unsigned long lastSms = 0;
bool smsSentOnce = false;
int readGas() {
long sum = 0;
for (int i = 0; i < 16; i++) {
sum += analogRead(GAS_PIN);
delay(5);
}
return sum / 16;
}
void sendSms(int level) {
if (!*ALERT_TO) return; // no phone number set: the siren still works
sim.println(F("AT+CMGF=1"));
delay(300);
sim.print(F("AT+CMGS=\""));
sim.print(ALERT_TO);
sim.println('"');
delay(500);
sim.print(F("GAS LEAK ALARM! Sensor level "));
sim.print(level);
sim.print(F(". Close the cylinder valve, open windows, no switches or flames."));
sim.write(26);
delay(4000);
}
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
Serial.begin(115200);
frSettingsBegin(SETTINGS);
if (!*ALERT_TO) Serial.println(F("SMS off — no phone number set. Flash from flashready.in or type it into the code."));
sim.begin(9600);
Serial.println(F("Warming up the MQ-6 for 3 minutes..."));
}
void loop() {
int level = readGas();
bool warm = millis() > WARM_UP_MS;
Serial.print(F("gas="));
Serial.println(level);
if (warm && level > ALARM_LEVEL) {
// Pulsed siren: 300 ms on, 200 ms off
digitalWrite(BUZZER_PIN, HIGH);
delay(300);
digitalWrite(BUZZER_PIN, LOW);
delay(200);
if (!smsSentOnce || millis() - lastSms > SMS_GAP_MS) {
sendSms(level);
lastSms = millis();
smsSentOnce = true;
}
} else {
digitalWrite(BUZZER_PIN, LOW);
delay(500);
}
}// LPG gas leak alarm with SMS — Arduino Uno R3 + MQ-6 + buzzer module + SIM800L
// MQ-6 analog output on A0, buzzer module on D8, SIM800L on D2/D3 (RXD through a divider).
// The sensor needs a few minutes of heating before readings mean anything.
#include <SoftwareSerial.h>
#include "fr_settings.h"
constexpr uint8_t GAS_PIN = A0;
constexpr uint8_t BUZZER_PIN = 8; // buzzer module I/O (high = on)
constexpr uint8_t SIM_RX = 2; // <- SIM800L TXD
constexpr uint8_t SIM_TX = 3; // -> divider -> SIM800L RXD
constexpr int ALARM_LEVEL = 400; // raw ADC reading; set it with the calibration steps
constexpr unsigned long WARM_UP_MS = 3UL * 60UL * 1000UL;
constexpr unsigned long SMS_GAP_MS = 10UL * 60UL * 1000UL; // at most one SMS per 10 minutes
const char* ALERT_TO = "+91XXXXXXXXXX";
// Filled in from the FlashReady flasher over USB (kept in EEPROM), or type the values above.
FrSetting SETTINGS[] = {{"alert_to", &ALERT_TO, true}};
SoftwareSerial sim(SIM_RX, SIM_TX);
unsigned long lastSms = 0;
bool smsSentOnce = false;
int readGas() {
long sum = 0;
for (int i = 0; i < 16; i++) {
sum += analogRead(GAS_PIN);
delay(5);
}
return sum / 16;
}
void sendSms(int level) {
if (!*ALERT_TO) return; // no phone number set: the siren still works
sim.println(F("AT+CMGF=1"));
delay(300);
sim.print(F("AT+CMGS=\""));
sim.print(ALERT_TO);
sim.println('"');
delay(500);
sim.print(F("GAS LEAK ALARM! Sensor level "));
sim.print(level);
sim.print(F(". Close the cylinder valve, open windows, no switches or flames."));
sim.write(26);
delay(4000);
}
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
Serial.begin(115200);
frSettingsBegin(SETTINGS);
if (!*ALERT_TO) Serial.println(F("SMS off — no phone number set. Flash from flashready.in or type it into the code."));
sim.begin(9600);
Serial.println(F("Warming up the MQ-6 for 3 minutes..."));
}
void loop() {
int level = readGas();
bool warm = millis() > WARM_UP_MS;
Serial.print(F("gas="));
Serial.println(level);
if (warm && level > ALARM_LEVEL) {
// Pulsed siren: 300 ms on, 200 ms off
digitalWrite(BUZZER_PIN, HIGH);
delay(300);
digitalWrite(BUZZER_PIN, LOW);
delay(200);
if (!smsSentOnce || millis() - lastSms > SMS_GAP_MS) {
sendSms(level);
lastSms = millis();
smsSentOnce = true;
}
} else {
digitalWrite(BUZZER_PIN, LOW);
delay(500);
}
}Arduino IDE में: fr_settings.h को .ino के साथ उसी फ़ोल्डर में रखें (टैब मेनू → New Tab)।
// fr_settings.h — FlashReady on-board settings (Wi-Fi name, phone number, ...) over USB.
//
// Why: the pre-built file on flashready.in is the same for everyone, so your own settings
// can't be inside it. Instead the board asks for them over USB serial and keeps them in
// its EEPROM. If you use Arduino IDE you can still type them into the constants in the
// sketch — this file then changes nothing except a 2-second pause at start-up.
//
// The protocol, in plain words (one text line each way, ending in \n, at 115200 baud):
// 1. Right after Serial.begin(115200) the board prints
// FR?SETTINGS v1 keys=wifi_ssid,wifi_pass missing=wifi_pass
// "missing" lists the required settings that still have no real value.
// 2. If nothing is missing it listens for 2 seconds and then carries on. If something is
// missing it keeps listening, printing the line again every 2 seconds, for up to
// 30 seconds; then it carries on anyway and the sketch skips whatever needs the
// missing value (an alarm still sounds, it just can't send the SMS).
// 3. The flasher answers FR!SET wifi_ssid=My%20Home&wifi_pass=secret
// (values URL-encoded). The board stores them and answers FR!OK, then carries on with
// the new values. Errors: FR!ERR unknown <key>, FR!ERR long <key> (nothing is stored).
// A key that is left out keeps its stored value; "key=" (empty) removes it.
// "FR!SET" with nothing after it means "carry on now": FR!OK, nothing is stored.
// 4. FR!CLEAR wipes everything that was stored, answers FR!OK and keeps listening.
// Other lines are ignored.
//
// A stored value wins over the constant in the sketch. A constant that is still a
// placeholder ("YOUR_SSID", "your-wifi", "+91XXXXXXXXXX") or empty counts as not set.
//
// EEPROM block at FR_SETTINGS_ADDR: 'F' 'R' 0x01, length (uint16, low byte first), then
// "key=value\n" lines. Uses FR_SETTINGS_SIZE bytes; move FR_SETTINGS_ADDR with a #define
// before the #include if your sketch uses EEPROM for something else.
//
// Use in a sketch:
// const char* WIFI_SSID = "YOUR_SSID";
// FrSetting SETTINGS[] = { {"wifi_ssid", &WIFI_SSID, true} }; // key, constant, required
// void setup() { Serial.begin(115200); frSettingsBegin(SETTINGS); ... WiFi.begin(WIFI_SSID, ...); }
// After frSettingsBegin() each constant points at the value to use ("" if none is set).
#pragma once
#include <Arduino.h>
#include <EEPROM.h>
#include <string.h>
#ifndef FR_SETTINGS_ADDR
#define FR_SETTINGS_ADDR 0
#endif
#ifndef FR_SETTINGS_SIZE
#if defined(__AVR__)
#define FR_SETTINGS_SIZE 96 // 2 KB of RAM on an Uno: a phone number fits easily
#else
#define FR_SETTINGS_SIZE 512
#endif
#endif
#ifndef FR_LINE_MAX
#if defined(__AVR__)
#define FR_LINE_MAX 128
#else
#define FR_LINE_MAX 1024
#endif
#endif
#define FR_MAX_KEYS 8
#define FR_WAIT_MAX_MS 30000UL // never wait longer than this for missing settings
#define FR_DATA_MAX (FR_SETTINGS_SIZE - 5)
struct FrSetting {
const char* key; // [a-z0-9_], as in settings.json
const char** value; // the sketch's constant; replaced by the value to use
bool required;
};
// Stored settings in RAM as "key=value\0key=value\0" (only non-empty values).
static char frData[FR_DATA_MAX + 1];
static uint16_t frLen = 0;
static inline void frEepromBegin() {
#if defined(ESP32) || defined(ESP8266)
static bool started = false;
if (!started) EEPROM.begin(FR_SETTINGS_ADDR + FR_SETTINGS_SIZE);
started = true;
#endif
}
static inline void frLoad() {
frEepromBegin();
frLen = 0;
frData[0] = 0;
if (EEPROM.read(FR_SETTINGS_ADDR) != 'F' || EEPROM.read(FR_SETTINGS_ADDR + 1) != 'R' || EEPROM.read(FR_SETTINGS_ADDR + 2) != 1) return;
uint16_t n = EEPROM.read(FR_SETTINGS_ADDR + 3) | (EEPROM.read(FR_SETTINGS_ADDR + 4) << 8);
if (n > FR_DATA_MAX) return;
for (uint16_t i = 0; i < n; i++) {
char c = EEPROM.read(FR_SETTINGS_ADDR + 5 + i);
frData[i] = c == '\n' ? '\0' : c;
}
frData[n] = 0;
frLen = n;
}
static inline void frPut(int addr, uint8_t b) {
#if defined(__AVR__)
EEPROM.update(addr, b);
#else
EEPROM.write(addr, b);
#endif
}
static inline void frSave() {
frEepromBegin();
frPut(FR_SETTINGS_ADDR, 'F');
frPut(FR_SETTINGS_ADDR + 1, 'R');
frPut(FR_SETTINGS_ADDR + 2, 1);
frPut(FR_SETTINGS_ADDR + 3, frLen & 0xFF);
frPut(FR_SETTINGS_ADDR + 4, frLen >> 8);
for (uint16_t i = 0; i < frLen; i++) frPut(FR_SETTINGS_ADDR + 5 + i, frData[i] ? frData[i] : '\n');
#if defined(ESP32) || defined(ESP8266)
EEPROM.commit();
#endif
}
// The stored value for key, or nullptr.
static inline const char* frStored(const char* key) {
size_t kl = strlen(key);
for (uint16_t i = 0; i < frLen; i += strlen(frData + i) + 1) {
if (strncmp(frData + i, key, kl) == 0 && frData[i + kl] == '=') return frData + i + kl + 1;
}
return nullptr;
}
// Empty, "your_…" / "YOUR-…" / "your …", or a "+91XXXX…" phone number.
static inline bool frIsPlaceholder(const char* v) {
if (!v || !*v) return true;
if (strncasecmp(v, "your", 4) == 0 && (v[4] == '_' || v[4] == '-' || v[4] == ' ')) return true;
return v[0] == '+' && strstr(v, "XXXX") != nullptr;
}
// The stored value for key, else the fallback constant; "" if that is a placeholder.
static inline const char* frSettingsGet(const char* key, const char* fallback) {
const char* v = frStored(key);
if (!v) v = fallback;
return frIsPlaceholder(v) ? "" : v;
}
static inline bool frIsMissing(const FrSetting& s) {
return s.required && !*frSettingsGet(s.key, *s.value);
}
static inline void frRequest(FrSetting* s, uint8_t n) {
Serial.print(F("FR?SETTINGS v1 keys="));
for (uint8_t i = 0; i < n; i++) {
if (i) Serial.print(',');
Serial.print(s[i].key);
}
Serial.print(F(" missing="));
bool first = true;
for (uint8_t i = 0; i < n; i++) {
if (!frIsMissing(s[i])) continue;
if (!first) Serial.print(',');
Serial.print(s[i].key);
first = false;
}
Serial.print('\n');
}
static inline void frReply(const __FlashStringHelper* what, const char* key) {
Serial.print(what);
Serial.print(key);
Serial.print('\n');
}
static inline int8_t frHex(char c) {
if (c >= '0' && c <= '9') return c - '0';
c |= 0x20;
return (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1;
}
// Percent-decodes in place. Drops NUL, CR and LF: they can't be stored.
static inline void frDecode(char* s) {
char* o = s;
for (; *s; s++) {
char c = *s;
if (c == '%' && frHex(s[1]) >= 0 && frHex(s[2]) >= 0) {
c = (char)(frHex(s[1]) << 4 | frHex(s[2]));
s += 2;
}
if (c != '\0' && c != '\n' && c != '\r') *o++ = c;
}
*o = 0;
}
// "k1=v1&k2=v2" (cut short if the line overflowed). Saves and returns true, or replies with an error.
static inline bool frSet(FrSetting* s, uint8_t n, char* p, bool overflow) {
const char* upd[FR_MAX_KEYS] = {};
while (*p) {
char* amp = strchr(p, '&');
bool last = !amp;
if (amp) *amp = 0;
char* eq = strchr(p, '=');
if (eq) *eq = 0;
if (*p) {
if (last && overflow) {
frReply(F("FR!ERR long "), p);
return false;
}
uint8_t i = 0;
while (i < n && strcmp(s[i].key, p) != 0) i++;
if (i == n) {
frReply(F("FR!ERR unknown "), p);
return false;
}
char* v = eq ? eq + 1 : p + strlen(p); // "key" alone = "key="
frDecode(v);
upd[i] = v;
}
if (last) break;
p = amp + 1;
}
// Build the new block: for each key, the new value or the one already stored.
char next[FR_DATA_MAX + 1];
uint16_t len = 0;
for (uint8_t i = 0; i < n; i++) {
const char* v = upd[i] ? upd[i] : frStored(s[i].key);
if (!v || !*v) continue;
size_t kl = strlen(s[i].key), vl = strlen(v);
if (len + kl + vl + 2 > FR_DATA_MAX) {
frReply(F("FR!ERR long "), s[i].key);
return false;
}
memcpy(next + len, s[i].key, kl);
next[len + kl] = '=';
memcpy(next + len + kl + 1, v, vl + 1);
len += kl + vl + 2;
}
memcpy(frData, next, len);
frData[len] = 0;
frLen = len;
frSave();
return true;
}
// Handles one received line. Returns true once settings were saved (boot carries on).
static inline bool frCommand(FrSetting* s, uint8_t n, char* line, bool overflow) {
if (strcmp(line, "FR!CLEAR") == 0) {
frLen = 0;
frData[0] = 0;
frSave();
Serial.print(F("FR!OK\n"));
return false;
}
if (strncmp(line, "FR!SET", 6) != 0 || (line[6] != ' ' && line[6] != 0)) return false; // not for us
char* p = line + 6;
while (*p == ' ') p++;
if (*p && !frSet(s, n, p, overflow)) return false; // empty FR!SET = carry on, store nothing
Serial.print(F("FR!OK\n"));
return true;
}
// Call right after Serial.begin(115200). Prints the request, waits as described at the top
// (30 s at most), then points every constant in the list at the value to use ("" = not set).
static inline void frSettingsBegin(FrSetting* s, uint8_t n) {
frLoad();
char line[FR_LINE_MAX];
uint16_t len = 0;
bool overflow = false;
frRequest(s, n);
unsigned long start = millis(), quietSince = start;
while (millis() - start < FR_WAIT_MAX_MS) {
int c = Serial.read();
if (c >= 0) {
quietSince = millis();
if (c == '\r') continue;
if (c != '\n') {
if (len < FR_LINE_MAX - 1) line[len++] = (char)c;
else overflow = true;
continue;
}
line[len] = 0;
bool saved = frCommand(s, n, line, overflow);
len = 0;
overflow = false;
if (saved) break;
continue;
}
if (millis() - quietSince < 2000) {
delay(1); // also keeps the ESP8266 watchdog fed
continue;
}
len = 0; // drop half a line
overflow = false;
bool missing = false;
for (uint8_t i = 0; i < n; i++) missing |= frIsMissing(s[i]);
if (!missing) break;
frRequest(s, n);
quietSince = millis();
}
for (uint8_t i = 0; i < n; i++) *s[i].value = frSettingsGet(s[i].key, *s[i].value);
}
template <size_t N>
static inline void frSettingsBegin(FrSetting (&s)[N]) {
static_assert(N <= FR_MAX_KEYS, "fr_settings.h: at most FR_MAX_KEYS settings");
frSettingsBegin(s, (uint8_t)N);
}// fr_settings.h — FlashReady on-board settings (Wi-Fi name, phone number, ...) over USB.
//
// Why: the pre-built file on flashready.in is the same for everyone, so your own settings
// can't be inside it. Instead the board asks for them over USB serial and keeps them in
// its EEPROM. If you use Arduino IDE you can still type them into the constants in the
// sketch — this file then changes nothing except a 2-second pause at start-up.
//
// The protocol, in plain words (one text line each way, ending in \n, at 115200 baud):
// 1. Right after Serial.begin(115200) the board prints
// FR?SETTINGS v1 keys=wifi_ssid,wifi_pass missing=wifi_pass
// "missing" lists the required settings that still have no real value.
// 2. If nothing is missing it listens for 2 seconds and then carries on. If something is
// missing it keeps listening, printing the line again every 2 seconds, for up to
// 30 seconds; then it carries on anyway and the sketch skips whatever needs the
// missing value (an alarm still sounds, it just can't send the SMS).
// 3. The flasher answers FR!SET wifi_ssid=My%20Home&wifi_pass=secret
// (values URL-encoded). The board stores them and answers FR!OK, then carries on with
// the new values. Errors: FR!ERR unknown <key>, FR!ERR long <key> (nothing is stored).
// A key that is left out keeps its stored value; "key=" (empty) removes it.
// "FR!SET" with nothing after it means "carry on now": FR!OK, nothing is stored.
// 4. FR!CLEAR wipes everything that was stored, answers FR!OK and keeps listening.
// Other lines are ignored.
//
// A stored value wins over the constant in the sketch. A constant that is still a
// placeholder ("YOUR_SSID", "your-wifi", "+91XXXXXXXXXX") or empty counts as not set.
//
// EEPROM block at FR_SETTINGS_ADDR: 'F' 'R' 0x01, length (uint16, low byte first), then
// "key=value\n" lines. Uses FR_SETTINGS_SIZE bytes; move FR_SETTINGS_ADDR with a #define
// before the #include if your sketch uses EEPROM for something else.
//
// Use in a sketch:
// const char* WIFI_SSID = "YOUR_SSID";
// FrSetting SETTINGS[] = { {"wifi_ssid", &WIFI_SSID, true} }; // key, constant, required
// void setup() { Serial.begin(115200); frSettingsBegin(SETTINGS); ... WiFi.begin(WIFI_SSID, ...); }
// After frSettingsBegin() each constant points at the value to use ("" if none is set).
#pragma once
#include <Arduino.h>
#include <EEPROM.h>
#include <string.h>
#ifndef FR_SETTINGS_ADDR
#define FR_SETTINGS_ADDR 0
#endif
#ifndef FR_SETTINGS_SIZE
#if defined(__AVR__)
#define FR_SETTINGS_SIZE 96 // 2 KB of RAM on an Uno: a phone number fits easily
#else
#define FR_SETTINGS_SIZE 512
#endif
#endif
#ifndef FR_LINE_MAX
#if defined(__AVR__)
#define FR_LINE_MAX 128
#else
#define FR_LINE_MAX 1024
#endif
#endif
#define FR_MAX_KEYS 8
#define FR_WAIT_MAX_MS 30000UL // never wait longer than this for missing settings
#define FR_DATA_MAX (FR_SETTINGS_SIZE - 5)
struct FrSetting {
const char* key; // [a-z0-9_], as in settings.json
const char** value; // the sketch's constant; replaced by the value to use
bool required;
};
// Stored settings in RAM as "key=value\0key=value\0" (only non-empty values).
static char frData[FR_DATA_MAX + 1];
static uint16_t frLen = 0;
static inline void frEepromBegin() {
#if defined(ESP32) || defined(ESP8266)
static bool started = false;
if (!started) EEPROM.begin(FR_SETTINGS_ADDR + FR_SETTINGS_SIZE);
started = true;
#endif
}
static inline void frLoad() {
frEepromBegin();
frLen = 0;
frData[0] = 0;
if (EEPROM.read(FR_SETTINGS_ADDR) != 'F' || EEPROM.read(FR_SETTINGS_ADDR + 1) != 'R' || EEPROM.read(FR_SETTINGS_ADDR + 2) != 1) return;
uint16_t n = EEPROM.read(FR_SETTINGS_ADDR + 3) | (EEPROM.read(FR_SETTINGS_ADDR + 4) << 8);
if (n > FR_DATA_MAX) return;
for (uint16_t i = 0; i < n; i++) {
char c = EEPROM.read(FR_SETTINGS_ADDR + 5 + i);
frData[i] = c == '\n' ? '\0' : c;
}
frData[n] = 0;
frLen = n;
}
static inline void frPut(int addr, uint8_t b) {
#if defined(__AVR__)
EEPROM.update(addr, b);
#else
EEPROM.write(addr, b);
#endif
}
static inline void frSave() {
frEepromBegin();
frPut(FR_SETTINGS_ADDR, 'F');
frPut(FR_SETTINGS_ADDR + 1, 'R');
frPut(FR_SETTINGS_ADDR + 2, 1);
frPut(FR_SETTINGS_ADDR + 3, frLen & 0xFF);
frPut(FR_SETTINGS_ADDR + 4, frLen >> 8);
for (uint16_t i = 0; i < frLen; i++) frPut(FR_SETTINGS_ADDR + 5 + i, frData[i] ? frData[i] : '\n');
#if defined(ESP32) || defined(ESP8266)
EEPROM.commit();
#endif
}
// The stored value for key, or nullptr.
static inline const char* frStored(const char* key) {
size_t kl = strlen(key);
for (uint16_t i = 0; i < frLen; i += strlen(frData + i) + 1) {
if (strncmp(frData + i, key, kl) == 0 && frData[i + kl] == '=') return frData + i + kl + 1;
}
return nullptr;
}
// Empty, "your_…" / "YOUR-…" / "your …", or a "+91XXXX…" phone number.
static inline bool frIsPlaceholder(const char* v) {
if (!v || !*v) return true;
if (strncasecmp(v, "your", 4) == 0 && (v[4] == '_' || v[4] == '-' || v[4] == ' ')) return true;
return v[0] == '+' && strstr(v, "XXXX") != nullptr;
}
// The stored value for key, else the fallback constant; "" if that is a placeholder.
static inline const char* frSettingsGet(const char* key, const char* fallback) {
const char* v = frStored(key);
if (!v) v = fallback;
return frIsPlaceholder(v) ? "" : v;
}
static inline bool frIsMissing(const FrSetting& s) {
return s.required && !*frSettingsGet(s.key, *s.value);
}
static inline void frRequest(FrSetting* s, uint8_t n) {
Serial.print(F("FR?SETTINGS v1 keys="));
for (uint8_t i = 0; i < n; i++) {
if (i) Serial.print(',');
Serial.print(s[i].key);
}
Serial.print(F(" missing="));
bool first = true;
for (uint8_t i = 0; i < n; i++) {
if (!frIsMissing(s[i])) continue;
if (!first) Serial.print(',');
Serial.print(s[i].key);
first = false;
}
Serial.print('\n');
}
static inline void frReply(const __FlashStringHelper* what, const char* key) {
Serial.print(what);
Serial.print(key);
Serial.print('\n');
}
static inline int8_t frHex(char c) {
if (c >= '0' && c <= '9') return c - '0';
c |= 0x20;
return (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1;
}
// Percent-decodes in place. Drops NUL, CR and LF: they can't be stored.
static inline void frDecode(char* s) {
char* o = s;
for (; *s; s++) {
char c = *s;
if (c == '%' && frHex(s[1]) >= 0 && frHex(s[2]) >= 0) {
c = (char)(frHex(s[1]) << 4 | frHex(s[2]));
s += 2;
}
if (c != '\0' && c != '\n' && c != '\r') *o++ = c;
}
*o = 0;
}
// "k1=v1&k2=v2" (cut short if the line overflowed). Saves and returns true, or replies with an error.
static inline bool frSet(FrSetting* s, uint8_t n, char* p, bool overflow) {
const char* upd[FR_MAX_KEYS] = {};
while (*p) {
char* amp = strchr(p, '&');
bool last = !amp;
if (amp) *amp = 0;
char* eq = strchr(p, '=');
if (eq) *eq = 0;
if (*p) {
if (last && overflow) {
frReply(F("FR!ERR long "), p);
return false;
}
uint8_t i = 0;
while (i < n && strcmp(s[i].key, p) != 0) i++;
if (i == n) {
frReply(F("FR!ERR unknown "), p);
return false;
}
char* v = eq ? eq + 1 : p + strlen(p); // "key" alone = "key="
frDecode(v);
upd[i] = v;
}
if (last) break;
p = amp + 1;
}
// Build the new block: for each key, the new value or the one already stored.
char next[FR_DATA_MAX + 1];
uint16_t len = 0;
for (uint8_t i = 0; i < n; i++) {
const char* v = upd[i] ? upd[i] : frStored(s[i].key);
if (!v || !*v) continue;
size_t kl = strlen(s[i].key), vl = strlen(v);
if (len + kl + vl + 2 > FR_DATA_MAX) {
frReply(F("FR!ERR long "), s[i].key);
return false;
}
memcpy(next + len, s[i].key, kl);
next[len + kl] = '=';
memcpy(next + len + kl + 1, v, vl + 1);
len += kl + vl + 2;
}
memcpy(frData, next, len);
frData[len] = 0;
frLen = len;
frSave();
return true;
}
// Handles one received line. Returns true once settings were saved (boot carries on).
static inline bool frCommand(FrSetting* s, uint8_t n, char* line, bool overflow) {
if (strcmp(line, "FR!CLEAR") == 0) {
frLen = 0;
frData[0] = 0;
frSave();
Serial.print(F("FR!OK\n"));
return false;
}
if (strncmp(line, "FR!SET", 6) != 0 || (line[6] != ' ' && line[6] != 0)) return false; // not for us
char* p = line + 6;
while (*p == ' ') p++;
if (*p && !frSet(s, n, p, overflow)) return false; // empty FR!SET = carry on, store nothing
Serial.print(F("FR!OK\n"));
return true;
}
// Call right after Serial.begin(115200). Prints the request, waits as described at the top
// (30 s at most), then points every constant in the list at the value to use ("" = not set).
static inline void frSettingsBegin(FrSetting* s, uint8_t n) {
frLoad();
char line[FR_LINE_MAX];
uint16_t len = 0;
bool overflow = false;
frRequest(s, n);
unsigned long start = millis(), quietSince = start;
while (millis() - start < FR_WAIT_MAX_MS) {
int c = Serial.read();
if (c >= 0) {
quietSince = millis();
if (c == '\r') continue;
if (c != '\n') {
if (len < FR_LINE_MAX - 1) line[len++] = (char)c;
else overflow = true;
continue;
}
line[len] = 0;
bool saved = frCommand(s, n, line, overflow);
len = 0;
overflow = false;
if (saved) break;
continue;
}
if (millis() - quietSince < 2000) {
delay(1); // also keeps the ESP8266 watchdog fed
continue;
}
len = 0; // drop half a line
overflow = false;
bool missing = false;
for (uint8_t i = 0; i < n; i++) missing |= frIsMissing(s[i]);
if (!missing) break;
frRequest(s, n);
quietSince = millis();
}
for (uint8_t i = 0; i < n; i++) *s[i].value = frSettingsGet(s[i].key, *s[i].value);
}
template <size_t N>
static inline void frSettingsBegin(FrSetting (&s)[N]) {
static_assert(N <= FR_MAX_KEYS, "fr_settings.h: at most FR_MAX_KEYS settings");
frSettingsBegin(s, (uint8_t)N);
}Compiled for uno on avr 1.8.8 · 3 Nov 2026
यह कैसे काम करता है
बंद रसोई में LPG लीक, दरवाज़े पर गंध आने से बहुत पहले ही ख़तरनाक हो जाती है। यह अलार्म फ़र्श के पास की हवा पर नज़र रखता है, जहाँ LPG जमा होती है, रुक-रुक कर बजने वाला सायरन चलाता है और SMS भेजता है — घर ख़ाली हो तब यह ख़ास काम आता है।
MQ-6 में एक छोटे हीटर पर tin-oxide का एलिमेंट होता है। LPG उसका रेज़िस्टेंस कम कर देती है, इसलिए A0 पर analog आउटपुट बढ़ जाता है। स्केच शोर कम करने के लिए 16 रीडिंग का औसत लेता है, और 3 मिनट के वार्म-अप के बाद ही अलार्म चालू करता है, क्योंकि ठंडा सेंसर ज़्यादा वैल्यू दिखाता है।
थ्रेशोल्ड पार होने पर D8 वाला बज़र 300 ms चालू / 200 ms बंद होकर बजता है — लगातार टोन से ज़्यादा आसानी से ध्यान खींचता है। पहला SMS तुरंत जाता है; उसके बाद स्केच हर 10 मिनट में ज़्यादा से ज़्यादा एक SMS भेजता है, ताकि लंबे लीक में SIM का बैलेंस ख़त्म न हो और फ़ोन पर ढेर सारे मैसेज न आएँ।
SIM800L पर भी वही वायरिंग नियम लागू हैं जो किसी भी 5 V Arduino प्रोजेक्ट पर: इसके serial इनपुट पर divider लगता है, और इसकी सप्लाई एक अलग 4.0 V buck मॉड्यूल से आती है जो 2 A के झटके संभाल सके।
समस्या समाधान
पावर-ऑन होते ही अलार्म बज जाता है
ठंडा सेंसर ज़्यादा वैल्यू दिखाता है। वार्म-अप पूरा होने दें; अगर साफ़ हवा में भी बजता है, तो ALARM_LEVEL को साफ़ हवा की स्थिर रीडिंग से थोड़ा ऊपर कर दें।
अलार्म के समय SMS नहीं आता
देखें कि SIM800L की LED धीरे-धीरे टिमटिमा रही है (हर 3 s = नेटवर्क पर रजिस्टर्ड), SIM में बैलेंस है, और ALERT_TO में नंबर +91 फ़ॉर्मेट में है।
Uno बार-बार रीसेट होता है
SIM800L के 2 A वाले झटके सप्लाई को नीचे खींच रहे हैं। उसे बड़े कैपेसिटर के साथ उसके अपने buck मॉड्यूल से पावर दें।
आगे क्या करें
- 12 V solenoid valve (normally open, relay के ज़रिए) जोड़ें जो गैस लाइन बंद कर दे — सिर्फ़ LPG के लिए रेटेड वाल्व ही लगाएँ।
- SMS के साथ नंबर पर कॉल (ATD) भी करें, ताकि कोई सोता न रह जाए।
- रिले पर एक exhaust fan जोड़ें — रिले फ़ैन के हिसाब से रेटेड हो, और वायरिंग इलेक्ट्रीशियन ही करे।



