/* Final integrated alarm with Security and Relay Control
- Timer1 = 10 ms tick
- WS2812 on A3 (10 LEDs)
- PIR sensors: 2,3,4
- Remote keys: 6,7,8,9 (debounced via hwMillis)
- Buzzer: 5
- Relay OUTPUT pins: 12, A0, A1, A2 (Controlled by Armed/Disarmed state)
- Battery relay control: A5 (output)
- Battery analog: A4 (input)
- SIM800 on SoftwareSerial(10,11)
*** تغییرات اعمال شده: ***
1. رله ها به عنوان OUTPUT تعریف شدند.
2. کنترل رلهها (Armed=LOW / Disarmed=HIGH) در توابع کنترلی اضافه شد.
3. سیستم امنیتی (رمز عبور، مدیریت ۴ مدیر، حالت راهاندازی) اضافه شد.
4. هشدار بحرانی باتری (زیر ۱۱.۵ ولت) اضافه شد.
*/
#include <EEPROM.h>
#include <TimerOne.h>
#include <avr/wdt.h>
#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>
// ------------------ hardware mapping ------------------
#define NUM_SENSORS 3
#define NUM_REMOTE_KEYS 4
const int sensorPins[NUM_SENSORS] = {2, 3, 4};
const int remotePins[NUM_REMOTE_KEYS] = {6, 7, 8, 9};
const int buzzerPin = 5;
// WS2812
#define LED_COUNT 10
#define LED_PIN A3
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// Relay OUTPUT pins
const int relayPins[4] = {12, A0, A1, A2};
// Battery
const int batteryRelayPin = A5; // control charger relay
const int batteryAnalogPin = A4; // read battery
const float batteryStartCharge = 12.4;
const float batteryStopCharge = 12.6;
const float batteryDividerRatio = 2.6;
const float BATTERY_CRITICAL_LOW = 11.5; // <--- آستانه هشدار بحرانی باتری
// SIM800 (SoftwareSerial)
SoftwareSerial sim800(10, 11); // RX, TX
// adminNumber String حذف شد و از آرایه adminNumbers استفاده میشود
// EEPROM addresses
#define EEPROM_ADDR_ARMED 0
#define EEPROM_ADDR_SILENT 1
#define EEPROM_MAGIC_ADDR 10
#define EEPROM_MAGIC_VALUE 0xAB
// <--- آدرس های جدید برای مدیریت امنیت
#define NUM_ADMINS 4
#define EEPROM_ADDR_ADMIN_START 30 // 4 * 16 bytes for numbers
#define EEPROM_ADDR_PASSWORD_START 100 // 8 bytes for password
#define EEPROM_ADDR_SENSOR_LATCHED 110
// ------------------ system state ------------------
bool systemArmed = false;
bool silentMode = false;
bool buzzerActive = false;
bool sensorTriggered = false;
bool sensorLatched[NUM_SENSORS] = {false};
bool remoteLock[NUM_REMOTE_KEYS] = {false};
bool isCharging = false;
unsigned long lastMotionTime = 0;
const unsigned long alarmDuration = 20000UL; // 20s
// <--- متغیرهای جدید برای مدیریت امنیت و باتری
String adminNumbers[NUM_ADMINS];
String systemPassword = "1234"; // رمز پیش فرض
bool setupMode = true; // در ابتدا سیستم در حالت راهاندازی است
bool lowBatteryWarningSent = false;
// PIR filter
int pirHitCount[NUM_SENSORS] = {0};
unsigned long pirLastWindow[NUM_SENSORS] = {0};
const unsigned long PIR_WINDOW = 600UL; // ms
const int PIR_REQUIRED_HITS = 2;
// beeper (using Timer1 ISR)
volatile bool beepRequested = false;
volatile int beepCount = 0;
// Timer tick: using Timer1 at 10 ms.
volatile unsigned long tickAcc = 0; // in milliseconds
unsigned long hwMillis() {
unsigned long v;
noInterrupts();
v = tickAcc;
interrupts();
return v;
}
// Remote debounce
unsigned long lastRemoteChange[NUM_REMOTE_KEYS] = {0};
const unsigned long REMOTE_DEBOUNCE = 30UL; // ms
// Call attempts
const int CALL_TRIES = 3;
// Battery timing
unsigned long lastBatteryCheck = 0;
const unsigned long BATTERY_INTERVAL_MS = 20UL; // requested 20 ms
// SIM buffer (we suppress raw printing)
String simBuf = "";
// ------------------ EEPROM helpers ------------------
void safeEEPROMWrite(int addr, byte value) {
if (EEPROM.read(addr) != value) EEPROM.write(addr, value);
}
void saveSystemState() {
safeEEPROMWrite(EEPROM_ADDR_ARMED, systemArmed ? 1 : 0);
safeEEPROMWrite(EEPROM_ADDR_SILENT, silentMode ? 1 : 0);
safeEEPROMWrite(EEPROM_MAGIC_ADDR, EEPROM_MAGIC_VALUE);
}
void loadSystemState() {
byte magic = EEPROM.read(EEPROM_MAGIC_ADDR);
if (magic == EEPROM_MAGIC_VALUE) {
systemArmed = EEPROM.read(EEPROM_ADDR_ARMED);
silentMode = EEPROM.read(EEPROM_ADDR_SILENT);
if (systemArmed) {
if (silentMode) Serial.println(F("✅ دزدگیر فعال است (سایلنت)"));
else Serial.println(F("✅ دزدگیر فعال است"));
} else {
Serial.println(F("❌ دزدگیر غیرفعال است"));
}
} else {
systemArmed = false;
silentMode = false;
saveSystemState();
Serial.println(F("⚠️ تنظیمات پیشفرض بهدلیل خرابی دادههای EEPROM بارگذاری شد"));
}
}
void saveSensorLatchState() {
for (int i = 0; i < NUM_SENSORS; i++) {
safeEEPROMWrite(EEPROM_ADDR_SENSOR_LATCHED + i, sensorLatched[i] ? 1 : 0);
}
}
void loadSensorLatchState() {
for (int i = 0; i < NUM_SENSORS; i++) {
sensorLatched[i] = EEPROM.read(EEPROM_ADDR_SENSOR_LATCHED + i);
}
}
// <--- توابع جدید EEPROM برای ذخیره/بارگذاری رشتهها (شمارهها و رمز عبور)
void EEPROMWriteStr(int addr, const String &value) {
int len = value.length();
if (len > 15) len = 15; // محدودیت 15 کاراکتر
EEPROM.write(addr, len);
for (int i = 0; i < len; i++) {
EEPROM.write(addr + 1 + i, value[i]);
}
EEPROM.write(addr + 1 + len, 0); // null terminator for safety
}
String EEPROMReadStr(int addr) {
int len = EEPROM.read(addr);
String value = "";
if (len > 15) len = 15; // جلوگیری از خواندن بیش از حد
for (int i = 0; i < len; i++) {
value += (char)EEPROM.read(addr + 1 + i);
}
return value;
}
// بارگذاری شمارهها و رمز عبور
void loadSecurityData() {
systemPassword = EEPROMReadStr(EEPROM_ADDR_PASSWORD_START);
if (systemPassword.length() < 4) systemPassword = "1234"; // رمز پیشفرض اگر خالی بود
for (int i = 0; i < NUM_ADMINS; i++) {
adminNumbers[i] = EEPROMReadStr(EEPROM_ADDR_ADMIN_START + i * 16);
}
// اگر شماره ادمین صفر وجود نداشت، در حالت راهاندازی میمانیم
setupMode = (adminNumbers[0].length() == 0);
if (setupMode) {
Serial.println(F("⚠️ در حالت راهاندازی اولیه: منتظر رمز عبور پیشفرض (1234)"));
} else {
Serial.print(F("✅ رمز عبور فعلی: ")); Serial.println(systemPassword);
}
}
// ذخیره شمارهها و رمز عبور
void saveSecurityData() {
EEPROMWriteStr(EEPROM_ADDR_PASSWORD_START, systemPassword);
for (int i = 0; i < NUM_ADMINS; i++) {
EEPROMWriteStr(EEPROM_ADDR_ADMIN_START + i * 16, adminNumbers[i]);
}
}
// ------------------ Timer1 ISR (10 ms tick) ------------------
void timerISR() {
tickAcc += 10UL;
static bool buzzerState = false;
static int intervalCounter = 0;
if (beepRequested && beepCount > 0) {
if (intervalCounter == 0) {
buzzerState = !buzzerState;
digitalWrite(buzzerPin, buzzerState ? HIGH : LOW);
if (!buzzerState && --beepCount == 0) {
beepRequested = false;
digitalWrite(buzzerPin, LOW);
}
intervalCounter = 2;
} else {
intervalCounter--;
}
}
}
void requestBeep(int times) {
noInterrupts();
if (!beepRequested) {
beepCount = times * 2;
beepRequested = true;
}
interrupts();
}
// ------------------ Alarm activation logic ------------------
void activateAlarm() {
if (!silentMode) {
buzzerActive = true;
digitalWrite(buzzerPin, HIGH);
} else {
// Silent alarm triggered
}
Serial.println(F("🚨 وضعیت آلارم فعال شد"));
}
// ------------------------------------------------------------
// ------------------ WS2812 control ------------------
void initStrip() {
strip.begin();
strip.setBrightness(50);
strip.show();
}
void setLED(int idx, bool on) {
if (idx < 0 || idx >= LED_COUNT) return;
if (on) strip.setPixelColor(idx, strip.Color(255,255,255));
else strip.setPixelColor(idx, strip.Color(0,0,0));
}
void updateStrip() {
setLED(0, systemArmed && !silentMode);
setLED(1, systemArmed && silentMode);
setLED(2, !systemArmed);
for (int i = 0; i < NUM_SENSORS; i++) {
setLED(3 + i, sensorLatched[i]);
}
// LED status reflects the actual relay state
for (int r = 0; r < 4; r++) {
bool state = digitalRead(relayPins[r]);
setLED(6 + r, state);
}
strip.show();
}
// ------------------ PIR reliable detection ------------------
bool isReliablePIR(int index) {
if (digitalRead(sensorPins[index]) == HIGH) {
unsigned long now = hwMillis();
if (now - pirLastWindow[index] > PIR_WINDOW) {
pirHitCount[index] = 1;
pirLastWindow[index] = now;
} else {
pirHitCount[index]++;
pirLastWindow[index] = now;
if (pirHitCount[index] >= PIR_REQUIRED_HITS) {
pirHitCount[index] = 0;
return true;
}
}
}
return false;
}
// ------------------ Battery management ------------------
int readBatteryAverage() {
long sum = 0;
for (int i = 0; i < 10; i++) sum += analogRead(batteryAnalogPin);
return sum / 10;
}
void checkBattery() {
static bool lastChargingState = false;
static int lastBatteryPercent = -1;
int rawBattery = readBatteryAverage();
float voltageBattery = (rawBattery * 5.0 / 1023.0) * batteryDividerRatio;
// --- مدیریت شارژر ---
if (!isCharging && voltageBattery < batteryStartCharge) {
digitalWrite(batteryRelayPin, HIGH);
isCharging = true;
} else if (isCharging && voltageBattery >= batteryStopCharge) {
digitalWrite(batteryRelayPin, LOW);
isCharging = false;
}
if (isCharging != lastChargingState) {
if (isCharging) Serial.println(F("🔋 شارژ باتری فعال شد"));
else Serial.println(F("✅ شارژ باتری قطع شد"));
lastChargingState = isCharging;
}
// --- هشدار باتری بحرانی ---
if (voltageBattery < BATTERY_CRITICAL_LOW && !lowBatteryWarningSent) {
// ارسال پیام هشدار به تمام مدیران
String msg = String(F("⚠️ Critical Low Battery: ")) + String(voltageBattery, 1) + F(" V. Please check backup power.");
for (int i = 0; i < NUM_ADMINS; i++) {
if (adminNumbers[i].length() > 0) {
simSendSMS(adminNumbers[i], msg);
}
}
lowBatteryWarningSent = true;
Serial.println(F("🚨 هشدار بحرانی باتری ارسال شد"));
} else if (voltageBattery >= batteryStopCharge) {
lowBatteryWarningSent = false;
}
// --- محاسبه درصد باتری ---
float percentage = ((voltageBattery - 11.0) / (12.6 - 11.0)) * 100.0;
percentage = constrain(percentage, 0, 100);
int percentInt = round(percentage);
if (percentInt != lastBatteryPercent) {
lastBatteryPercent = percentInt;
Serial.print(F("🔋 درصد شارژ باتری: "));
Serial.print(percentInt);
Serial.println(F(" %"));
}
}
// ------------------ SIM800 minimal helpers ------------------
void simSendRaw(const __FlashStringHelper *cmd) {
sim800.println(cmd);
}
String simReadLineNonBlocking(unsigned long timeoutMs) {
unsigned long start = hwMillis();
String line = "";
while (hwMillis() - start < timeoutMs) {
while (sim800.available()) {
char c = sim800.read();
if (c == '\r') continue;
if (c == '\n') {
if (line.length() == 0) continue;
return line;
} else line += c;
}
}
return "";
}
bool simWaitForSubstring(const __FlashStringHelper *needle, unsigned long timeoutMs) {
unsigned long start = hwMillis();
String buf = "";
while (hwMillis() - start < timeoutMs) {
while (sim800.available()) {
char c = sim800.read();
buf += c;
if (buf.indexOf((const char*)needle) >= 0) return true;
if (buf.length() > 512) buf = buf.substring(buf.length()-256);
}
}
return false;
}
bool simInit() {
simSendRaw(F("AT"));
if (!simWaitForSubstring(F("OK"), 2000)) return false;
simSendRaw(F("ATE0")); simWaitForSubstring(F("OK"), 1000);
simSendRaw(F("AT+CMGF=1")); simWaitForSubstring(F("OK"), 1000);
simSendRaw(F("AT+CNMI=2,1,0,0,0")); simWaitForSubstring(F("OK"), 1000);
Serial.println(F("[SIM] ماژول آماده شد"));
return true;
}
// send SMS; sends to the specified number
bool simSendSMS(const String &number, const String &text) {
if (number.length() < 5) {
Serial.println(F("[SIM] خطا در ارسال SMS: شماره نامعتبر"));
return false;
}
sim800.print(F("AT+CMGS=\""));
sim800.print(number);
sim800.println(F("\""));
if (!simWaitForSubstring(F(">"), 2000)) {
Serial.println(F("[SIM] خطا در ارسال SMS (prompt)"));
return false;
}
sim800.print(text);
sim800.write(26); // CTRL+Z
if (simWaitForSubstring(F("OK"), 8000)) {
Serial.println(F("[SIM] SMS ارسال شد"));
return true;
} else {
Serial.println(F("[SIM] ارسال SMS تایید نشد"));
return false;
}
}
bool simCallAttempt(const String &number, unsigned long ringTimeoutMs) {
sim800.print(F("ATD"));
sim800.print(number);
sim800.println(F(";"));
unsigned long start = hwMillis();
while (hwMillis() - start < ringTimeoutMs) {
String line = simReadLineNonBlocking(200);
if (line.length() == 0) continue;
if (line.indexOf(F("NO CARRIER")) >= 0) return false;
if (line.indexOf(F("BUSY")) >= 0) return false;
if (line.indexOf(F("NO ANSWER")) >= 0) return false;
if (line.indexOf(F("CONNECT")) >= 0) return true;
if (line.indexOf(F("VOICE")) >= 0) return true;
if (line.indexOf(F("ANSWER")) >= 0) return true;
}
return false;
}
void simHangup() {
simSendRaw(F("ATH"));
simWaitForSubstring(F("OK"), 800);
}
bool simReadSMSByIndex(int index, String &outFrom, String &outBody) {
sim800.print(F("AT+CMGR="));
sim800.println(index);
unsigned long start = hwMillis();
String header = "";
String body = "";
bool headerRead = false;
while (hwMillis() - start < 3000) {
String line = simReadLineNonBlocking(300);
if (line.length() == 0) continue;
if (!headerRead && line.startsWith(F("+CMGR:"))) {
header = line; headerRead = true; continue;
} else if (headerRead) {
body = line; break;
}
}
if (!headerRead) return false;
int firstQuote = header.indexOf('"');
int secondQuote = header.indexOf('"', firstQuote+1);
int thirdQuote = header.indexOf('"', secondQuote+1);
int fourthQuote = header.indexOf('"', thirdQuote+1);
String phone = "";
if (thirdQuote >= 0 && fourthQuote > thirdQuote) phone = header.substring(thirdQuote+1, fourthQuote);
else phone = String("");
outFrom = phone;
outBody = body;
sim800.print(F("AT+CMGD="));
sim800.println(index);
simWaitForSubstring(F("OK"), 1000);
return true;
}
// ------------------ Helper Security Functions ------------------
// بررسی می کند که آیا شماره فرستنده، شماره ادمین است
int findAdminIndex(const String &number) {
for (int i = 0; i < NUM_ADMINS; i++) {
// از indexOf استفاده می کنیم تا تطابق جزئی (با یا بدون کد کشور) را پوشش دهد
if (adminNumbers[i].length() > 0 && adminNumbers[i].indexOf(number) >= 0) {
return i; // پیدا شد
}
}
return -1; // پیدا نشد
}
// بررسی می کند که آیا دستور از شماره مدیر ارسال شده است
bool isAdmin(const String &from) {
return findAdminIndex(from) != -1;
}
// ------------------ Relay Control Function ------------------
void updateRelays(bool armed) {
int relayState = armed ? LOW : HIGH; // Armed=LOW, Disarmed=HIGH
for (int r = 0; r < 4; r++) {
digitalWrite(relayPins[r], relayState);
}
}
// ------------------ SMS processing ------------------
void processIncomingSMS(const String &from, const String &text) {
Serial.print(F("[SMS] دریافت از: ")); Serial.println(from);
Serial.print(F("[SMS] متن: ")); Serial.println(text);
String cmd = text;
for (unsigned int i = 0; i < cmd.length(); i++) cmd.setCharAt(i, toupper(cmd.charAt(i)));
// --- حالت ۱: راهاندازی اولیه ---
if (setupMode) {
String defaultPassCmd = String(F("PASS:")) + systemPassword;
if (cmd.indexOf(defaultPassCmd) >= 0) {
// تنظیم مدیر اول و خروج از حالت راهاندازی
adminNumbers[0] = from;
setupMode = false;
saveSecurityData();
updateRelays(true); // ایمنی: سیستم در حالت Armed شروع به کار می کند
updateStrip();
simSendSMS(from, F("✅ Welcome! Your number is now Admin 1. System is Armed."));
Serial.println(F("✅ راهاندازی اولیه موفقیتآمیز بود."));
} else {
simSendSMS(from, F("⚠️ System Setup Mode. Send PASS:1234 to initialize."));
}
return;
}
// --- حالت ۲: احراز هویت (بررسی مجوز) ---
if (!isAdmin(from)) {
Serial.println(F("[SMS] فرستنده مجاز نیست -> نادیده گرفته شد"));
// simSendSMS(from, F("🚫 Unauthorized."));
return;
}
// --- حالت ۳: دستورات مدیریتی جدید (تغییر رمز/مدیریت مدیر) ---
if (cmd.startsWith(F("PASS:"))) {
String newPass = text.substring(5);
newPass.trim();
if (newPass.length() >= 4 && newPass.length() <= 15) {
systemPassword = newPass;
saveSecurityData();
simSendSMS(from, F("🔑 Password changed successfully."));
} else {
simSendSMS(from, F("⚠️ Password must be between 4 and 15 chars long."));
}
} else if (cmd.startsWith(F("ADD:"))) {
String newAdmin = text.substring(4);
newAdmin.trim();
int emptyIdx = -1;
for (int i = 0; i < NUM_ADMINS; i++) {
if (adminNumbers[i].length() == 0) { emptyIdx = i; break; }
}
if (emptyIdx != -1) {
adminNumbers[emptyIdx] = newAdmin;
saveSecurityData();
simSendSMS(from, String(F("✅ Admin ")) + String(emptyIdx + 1) + F(" added: ") + newAdmin);
} else {
simSendSMS(from, F("⚠️ Maximum 4 admins already added. Delete one first."));
}
} else if (cmd.startsWith(F("DEL:"))) {
String targetNum = text.substring(4);
targetNum.trim();
int idx = findAdminIndex(targetNum);
if (idx != -1) {
// جلوگیری از حذف آخرین مدیر
int activeAdmins = 0;
for(int i=0; i < NUM_ADMINS; i++) if(adminNumbers[i].length() > 0) activeAdmins++;
if (activeAdmins <= 1) {
simSendSMS(from, F("🚫 Cannot delete the last admin. Must have at least one."));
} else {
adminNumbers[idx] = "";
saveSecurityData();
simSendSMS(from, String(F("🗑️ Admin ")) + String(idx + 1) + F(" removed."));
}
} else {
simSendSMS(from, F("⚠️ Number not found in admin list."));
}
}
// --- حالت ۴: دستورات عملیاتی (ARM/DISARM/STATUS و غیره) ---
else if (cmd.indexOf(F("ARM")) >= 0) {
systemArmed = true; silentMode = false; saveSystemState();
updateRelays(true);
updateStrip();
simSendSMS(from, F("✅ Alarm Armed (Normal)"));
} else if (cmd.indexOf(F("DISARM")) >= 0) {
systemArmed = false; silentMode = false; buzzerActive = false; digitalWrite(buzzerPin, LOW);
updateRelays(false);
saveSystemState(); updateStrip();
simSendSMS(from, F("❌ Alarm Disarmed"));
} else if (cmd.indexOf(F("SILENT")) >= 0) {
systemArmed = true; silentMode = true; saveSystemState();
updateRelays(true);
updateStrip();
simSendSMS(from, F("🔇 Alarm Armed (Silent)"));
}
else if (cmd.indexOf(F("STATUS")) >= 0) {
String st = "";
if (!systemArmed) st += F("Alarm: Disarmed\n");
else { st += F("Alarm: Armed "); if (silentMode) st += F("(Silent)\n"); else st += F("(Normal)\n"); }
int raw = readBatteryAverage();
float vs = (raw * 5.0 / 1023.0) * batteryDividerRatio;
st += String(F("Battery: ")) + String(vs,2) + F(" V\n");
st += String(F("Password: ")) + systemPassword + F("\n");
for (int i=0; i < NUM_ADMINS; i++) {
st += String(F("Admin ")) + String(i+1) + F(": ") + (adminNumbers[i].length() > 0 ? adminNumbers[i] : F("Empty")) + F("\n");
}
for (int i = 0; i < NUM_SENSORS; i++) {
st += String(F("S")) + String(i+1) + F(": ") + (sensorLatched[i] ? F("Latched\n") : (digitalRead(sensorPins[i]) ? F("Active\n") : F("OK\n")));
}
for (int r = 0; r < 4; r++) st += String(F("R")) + String(r+1) + F(": ") + (digitalRead(relayPins[r]) ? F("ON\n") : F("OFF\n"));
simSendSMS(from, st);
} else if (cmd.indexOf(F("BATTERY")) >= 0) {
int raw = readBatteryAverage();
float vs = (raw * 5.0 / 1023.0) * batteryDividerRatio;
simSendSMS(from, String(F("Battery: ")) + String(vs,2) + F(" V"));
} else if (cmd.indexOf(F("RELAYS")) >= 0) {
String st = "";
for (int r = 0; r < 4; r++) st += String(F("R")) + String(r+1) + F(": ") + (digitalRead(relayPins[r]) ? F("ON\n") : F("OFF\n"));
simSendSMS(from, st);
} else if (cmd.indexOf(F("RESET")) >= 0) {
for (int i = 0; i < NUM_SENSORS; i++) sensorLatched[i] = false;
saveSensorLatchState(); updateStrip();
simSendSMS(from, F("Sensors reset"));
} else if (cmd.indexOf(F("REBOOT")) >= 0) {
simSendSMS(from, F("Rebooting..."));
wdt_enable(WDTO_15MS);
while (1) {}
} else {
simSendSMS(from, F("Unknown command"));
}
}
// ------------------ Remote handling ------------------
void handleRemoteCommand(int key) {
if (setupMode) { // ریموت در حالت راهاندازی اولیه غیرفعال است
Serial.println(F("❌ ریموت در حالت Setup غیرفعال است"));
return;
}
switch (key) {
case 0: // ARM (Normal)
if (!systemArmed || silentMode) {
systemArmed = true; silentMode = false; sensorTriggered = false;
for (int i=0;i<NUM_SENSORS;i++){ sensorLatched[i]=false; digitalWrite(buzzerPin, LOW); }
updateRelays(true);
saveSystemState(); requestBeep(1);
Serial.println(F("✅ دزدگیر فعال شد (عادی)"));
}
break;
case 1: // DISARM
if (systemArmed) {
systemArmed = false; silentMode = false; buzzerActive = false; sensorTriggered = false;
digitalWrite(buzzerPin, LOW);
updateRelays(false);
saveSystemState(); requestBeep(1);
Serial.println(F("❌ دزدگیر غیرفعال شد"));
}
break;
case 2: // SILENT ARM
if (!systemArmed || !silentMode) {
systemArmed = true; silentMode = true; sensorTriggered = false;
for (int i=0;i<NUM_SENSORS;i++){ sensorLatched[i]=false; }
updateRelays(true);
saveSystemState(); requestBeep(1);
Serial.println(F("🔇 دزدگیر فعال شد (سایلنت)"));
}
break;
case 3: // MANUAL BUZZER OFF
if (buzzerActive) {
buzzerActive = false; digitalWrite(buzzerPin, LOW); requestBeep(1);
Serial.println(F("🛑 آژیر دستی قطع شد"));
}
break;
}
updateStrip();
}
// ------------------ setup ------------------
void setupPins() {
pinMode(buzzerPin, OUTPUT); digitalWrite(buzzerPin, LOW);
for (int i = 0; i < NUM_SENSORS; i++) pinMode(sensorPins[i], INPUT);
for (int i = 0; i < NUM_REMOTE_KEYS; i++) pinMode(remotePins[i], INPUT);
for (int r = 0; r < 4; r++) {
pinMode(relayPins[r], OUTPUT);
digitalWrite(relayPins[r], LOW);
}
pinMode(batteryRelayPin, OUTPUT); digitalWrite(batteryRelayPin, LOW);
}
void setup() {
wdt_enable(WDTO_8S);
Serial.begin(9600);
sim800.begin(9600);
Serial.println(F("🛡️ دزدگیر راهاندازی شد"));
initStrip();
setupPins();
Timer1.initialize(10000);
Timer1.attachInterrupt(timerISR);
loadSystemState();
loadSensorLatchState();
loadSecurityData(); // بارگذاری دادههای امنیتی
if (setupMode) {
updateRelays(true); // در حالت Setup برای ایمنی خاموش بماند
} else if (!systemArmed) {
for (int i=0;i<NUM_SENSORS;i++){ sensorLatched[i]=false; }
updateRelays(false); // Disarmed -> Rels ON
} else {
updateRelays(true); // Armed -> Rels OFF
}
updateStrip();
if (!simInit()) Serial.println(F("[SIM] خطا در آمادهسازی ماژول SIM800"));
checkBattery();
}
// ------------------ Main loop ------------------
void loop() {
wdt_reset();
unsigned long now = hwMillis();
// --- handle SIM lines (SMS/Calls) ---
while (sim800.available()) {
String line = simReadLineNonBlocking(50);
if (line.length() == 0) break;
if (line.indexOf(F("+CMTI:")) >= 0) {
int comma = line.indexOf(',');
if (comma >= 0) {
String idxStr = line.substring(comma+1); idxStr.trim();
int idx = idxStr.toInt();
String from, body;
if (simReadSMSByIndex(idx, from, body)) {
processIncomingSMS(from, body);
}
}
} else {
// ignore other unsolicited raw lines (RING, etc.)
}
}
// poll unread SMS
static unsigned long lastSMSPoll = 0;
if (now - lastSMSPoll >= 3000UL) {
lastSMSPoll = now;
// In a real system, you'd parse CMGL output carefully, but for brevity:
// simSendRaw(F("AT+CMGL=\"REC UNREAD\""));
}
// --- remote debouncing (no delay) ---
for (int i = 0; i < NUM_REMOTE_KEYS; i++) {
int v = digitalRead(remotePins[i]);
if (v == HIGH && !remoteLock[i]) {
if (hwMillis() - lastRemoteChange[i] > REMOTE_DEBOUNCE) {
handleRemoteCommand(i);
remoteLock[i] = true;
}
lastRemoteChange[i] = hwMillis();
} else if (v == LOW) {
remoteLock[i] = false;
}
}
// --- sensor detection (only when armed AND not in setup mode) ---
if (systemArmed && !setupMode) {
for (int i = 0; i < NUM_SENSORS; i++) {
if (isReliablePIR(i)) {
sensorTriggered = true;
lastMotionTime = hwMillis();
activateAlarm();
if (!sensorLatched[i]) {
sensorLatched[i] = true;
saveSensorLatchState();
Serial.print(F("🚨 تحریک از سنسور "));
Serial.println(i + 1);
updateStrip();
}
// Call attempts to Admin 1
if(adminNumbers[0].length() > 0) {
bool answered = false;
for (int attempt = 1; attempt <= CALL_TRIES; attempt++) {
Serial.print(F("تماس تلاش ")); Serial.println(attempt);
bool ans = simCallAttempt(adminNumbers[0], 12000UL); // Call Admin 1
simHangup();
if (ans) {
Serial.println(F("پاسخ تماس"));
answered = true;
break;
} else {
if (attempt < CALL_TRIES) {
unsigned long ws = hwMillis();
while (hwMillis() - ws < 1000UL) {
if (sim800.available()) simReadLineNonBlocking(10);
}
}
}
}
// Send SMS alert to Admin 1
String msg = String(F("Alarm: Sensor ")) + String(i+1) + F(" triggered.");
simSendSMS(adminNumbers[0], msg);
}
}
}
}
// --- auto-stop buzzer ---
if (buzzerActive && (hwMillis() - lastMotionTime > alarmDuration)) {
buzzerActive = false;
digitalWrite(buzzerPin, LOW);
Serial.println(F("🔕 آژیر خودکار قطع شد"));
}
// --- battery check every 20 ms ---
if (now - lastBatteryCheck >= BATTERY_INTERVAL_MS) {
lastBatteryCheck = now;
checkBattery();
}
// --- update WS2812 ---
updateStrip();
}