#include <Keypad.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Preferences.h>
#include <Adafruit_NeoPixel.h>
#define NUM_LEDS 16
#define LED_PIN 14
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int buzzerPin = 13;
Preferences prefs; // شیء Preferences برای خواندن/نوشتن تنظیمات
// تعریف پین های روتاری انکدر (پیشنهادی: 25,26,27)
#define ENCODER_CLK 25
#define ENCODER_DT 26
#define ENCODER_SW 27
// تعریف مخاطبین (ذخیره سازی بایت-بایتی)
const int MAX_CONTACTS = 10;
struct Contact {
char name[16]; // حداکثر 15 حرف + NULL
char number[20]; // شماره تلفن
uint8_t r; // رنگ RGB واقعی - قرمز
uint8_t g; // رنگ RGB واقعی - سبز
uint8_t b; // رنگ RGB واقعی - آبی
bool used; // آیا این رکورد استفاده شده؟
};
// helper برای گرفتن رنگ از یک Contact
Contact contacts[MAX_CONTACTS]; // ← این باید قبل از هر تابعی که از contacts استفاده میکنه باشه
int contactCount = 0;
int currentContactIndex = 0;
bool inContactsMenu = false; // آیا داخل منوی مخاطبین هستیم؟
// ---- NEW: مدیریت تماس و نگهداشتن کلید انکدر ----
bool callActive = false; // آیا الان تماس فعال هست؟
int callingContactIndex = -1; // اندیس مخاطبی که در حال تماس است
unsigned long encoderPressStart = 0; // زمان شروع فشردن انکدر
const unsigned long LONG_PRESS_MS = 3000; // آستانه نگهداشتن (۳۰۰۰ ms)
// NEW: مدیریت نمایش تماس
unsigned long callStartTime = 0; // زمان شروع تماس
const unsigned long callDisplayTime = 10000; // ۱۰ ثانیه نمایش LED و LCD هنگام تماس
void checkCallTimeout() {
if (!callActive) return;
unsigned long now = millis();
if (now - callStartTime >= callDisplayTime) {
// خاموش کردن LED ها
strip.clear();
strip.show();
// پایان تماس منطقی
callActive = false;
int endedIdx = callingContactIndex;
callingContactIndex = -1;
Serial.println("Call display ended (timeout). Restoring previous screen.");
// اگر آلارم فعال نیست، صفحه قبلی را بازگردان
restorePreviousScreen();
}
}
enum Mode {
MODE_IDLE, MODE_WAIT_ACTION, MODE_SET_ID, MODE_SET_INTERVAL, MODE_DELETE, MODE_RUNNING
};
Mode mode = MODE_IDLE;
bool systemRunning = false;
bool allowInput = false;
bool showingList = false;
String tempInput = "";
int displayIndex = 0;
// ذخیره وضعیت صفحه
Mode savedMode = MODE_IDLE;
bool savedSystemRunning = false;
bool savedAllowInput = false;
bool savedShowingList = false;
String savedTempInput = "";
int savedDisplayIndex = 0;
void displayNextAlarm();
void handleKey(char key);
void savePreviousScreenState();
void sendBlynkCall(int idx);
void setLEDsForContact(int idx);
// وضعیت آلارمها
unsigned long snoozeTime = 5000;
unsigned long alertDuration = 3000;
bool alertActive = false;
int currentAlertID = -1;
unsigned long alertStart = 0;
void restorePreviousScreen() {
// Restore logical state
mode = savedMode;
systemRunning = savedSystemRunning;
allowInput = savedAllowInput;
showingList = savedShowingList;
tempInput = savedTempInput;
displayIndex = savedDisplayIndex;
// سپس محتویات LCD را بر اساس حالت مشابه قبل بازسازی میکنیم
lcd.clear();
if (alertActive) {
lcd.setCursor(0,0);
lcd.print("Alarm ID:");
if (currentAlertID != -1) lcd.print(currentAlertID);
} else if (showingList) {
displayNextAlarm(); // این خودش lcd را آپدیت میکند
} else if (systemRunning) {
lcd.setCursor(0,0);
lcd.print("System Running");
} else if (mode == MODE_WAIT_ACTION) {
lcd.setCursor(0,0);
lcd.print("A:Add D:Delete");
lcd.setCursor(0,1);
lcd.print("00:List");
} else if (mode == MODE_SET_ID) {
lcd.setCursor(0,0);
lcd.print("Add ID:");
lcd.setCursor(0,1);
lcd.print("01:Back");
} else {
lcd.setCursor(0,0);
lcd.print("Press * to Start");
}
}
void savePreviousScreenState() {
savedMode = mode;
savedSystemRunning = systemRunning;
savedAllowInput = allowInput;
savedShowingList = showingList;
savedTempInput = tempInput;
savedDisplayIndex = displayIndex;
Serial.println("Screen state saved.");
}
// helper برای گرفتن رنگ از یک Contact
uint32_t contactColor(int idx) {
if (idx < 0 || idx >= MAX_CONTACTS) return strip.Color(0, 0, 0); // سیاه
return strip.Color(contacts[idx].r, contacts[idx].g, contacts[idx].b);
}
// ذخیره/بارگذاری مخاطبین
void saveContactsToPrefs() {
prefs.putBytes("contacts", contacts, sizeof(contacts));
Serial.println("Contacts saved.");
}
void loadContactsFromPrefs() {
if (prefs.isKey("contacts")) {
size_t read = prefs.getBytes("contacts", contacts, sizeof(contacts));
if (read == sizeof(contacts)) {
contactCount = 0;
for (int i = 0; i < MAX_CONTACTS; i++) {
if (contacts[i].used) contactCount++;
}
return;
}
}
memset(contacts, 0, sizeof(contacts));
strcpy(contacts[0].name, "Daughter");
strcpy(contacts[0].number, "09120000001");
contacts[0].r = 255; contacts[0].g = 0; contacts[0].b = 0; // RED
contacts[0].used = true;
strcpy(contacts[1].name, "Son");
strcpy(contacts[1].number, "09120000002");
contacts[1].r = 0; contacts[1].g = 0; contacts[1].b = 255; // BLUE
contacts[1].used = true;
contactCount = 2;
saveContactsToPrefs();
}
// توابع جدید (اعلان)
void saveContactsToPrefs();
void loadContactsFromPrefs();
void showContactOnLCD(int idx);
void handleEncoder();
// قطع تماس
void sendBlynkHangup(int idx) {
callActive = false;
callingContactIndex = -1;
Serial.print("Blynk notify: hangup ");
Serial.println(contacts[idx].name);
// اینجا میتوان فراخوانی Blynk یا API واقعی را اضافه کرد
}
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {19, 18, 5, 17};
byte colPins[COLS] = {16, 4, 2, 15};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
struct Alarm {
int interval;
unsigned long lastTrigger;
bool active;
bool snoozing;
unsigned long snoozeStart;
};
const int MAX_ALARMS = 100;
Alarm alarms[MAX_ALARMS];
int currentID = -1;
unsigned long listShowTime = 0;
const unsigned long listTimeout = 4000; // زمان نمایش هر آلارم (۴ ثانیه)
void displayNextAlarm(); // <- اعلام تابع
// ذخیره/بارگذاری آلارمها
// دادههای پایدار (فقط چیزهایی که باید بعد از ریست حفظ شوند)
struct AlarmData {
int interval;
bool active;
};
void saveAlarmsToPrefs() {
AlarmData data[MAX_ALARMS];
for (int i = 0; i < MAX_ALARMS; i++) {
data[i].interval = alarms[i].interval;
data[i].active = alarms[i].active;
}
prefs.putBytes("alarms", data, sizeof(data));
prefs.end();
prefs.begin("medband", false);
}
void loadAlarmsFromPrefs() {
AlarmData data[MAX_ALARMS];
if (prefs.isKey("alarms")) {
size_t read = prefs.getBytes("alarms", data, sizeof(data));
if (read == sizeof(data)) {
for (int i = 0; i < MAX_ALARMS; i++) {
alarms[i].interval = data[i].interval;
alarms[i].active = data[i].active;
// 👇 فیلدهای زمان اجرای موقت دوباره مقداردهی میشوند
alarms[i].lastTrigger = millis();
alarms[i].snoozing = false;
alarms[i].snoozeStart = 0;
}
}
} else {
memset(alarms, 0, sizeof(alarms));
}
}
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
// FastLED init (حلقه WS2812)
strip.begin();
strip.show(); // همه LEDها رو خاموش میکنه
pinMode(buzzerPin, OUTPUT);
// مقداردهی Preferences و بارگذاری آلارمها و مخاطبین
prefs.begin("medband", false); // namespace = "medband"، false -> read/write
loadAlarmsFromPrefs(); // بارگذاری آلارمها از حافظه
loadContactsFromPrefs(); // بارگذاری مخاطبین
// تنظیم پینهای روتاری
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
pinMode(ENCODER_SW, INPUT_PULLUP); // دکمه به GND وصل میشه (فعال با LOW)
int activeCount = 0;
for (int i = 0; i < MAX_ALARMS; i++) {
if (alarms[i].active) activeCount++;
}
Serial.print("Loaded alarms: ");
Serial.println(activeCount);
Serial.print("Loaded contacts: ");
Serial.println(contactCount);
lcd.setCursor(0, 0);
lcd.print("Press * to Start");
}
void loop() {
// بررسی پایان تماس (timeout)
checkCallTimeout();
// پردازش روتاری انکدر
handleEncoder();
// پردازش کیپد
char key = keypad.getKey();
if (key) {
handleKey(key);
}
// مدیریت آلارمها
if (systemRunning && !alertActive) {
for (int i = 0; i < MAX_ALARMS; i++) {
if (alarms[i].active && !alarms[i].snoozing) {
if (millis() - alarms[i].lastTrigger >= alarms[i].interval) {
alertActive = true;
currentAlertID = i;
alertStart = millis();
tone(buzzerPin, 1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Alarm ID:");
lcd.print(i);
break;
}
}
}
}
}
void handleKey(char key) {
if (key != '*' && (
(systemRunning && !allowInput && key != 'B' && key != 'C') ||
(!systemRunning && !allowInput)
)) return;
if (key == '*') {
if (allowInput && tempInput == "00") {
tempInput = "";
showingList = true;
displayIndex = 0;
displayNextAlarm();
return;
}
if (!allowInput) {
allowInput = true;
systemRunning = false;
mode = MODE_WAIT_ACTION;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("A:Add D:Delete");
lcd.setCursor(0, 1);
lcd.print("00:List");
return;
}
else if (allowInput) {
if (mode == MODE_SET_ID && tempInput != "") {
currentID = tempInput.toInt();
tempInput = "";
if (currentID < 0 || currentID >= MAX_ALARMS) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid ID!");
lcd.setCursor(0, 1);
lcd.print("Max ID: ");
lcd.print(MAX_ALARMS - 1);
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Add ID");
lcd.setCursor(0, 1);
lcd.print("01=Back");
return;
}
// ✅ بررسی تکراری بودن ID
if (alarms[currentID].active) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID Already Exists!");
lcd.setCursor(0, 1);
lcd.print("New ID or 01:Back");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Add ID:");
lcd.setCursor(0, 1);
lcd.print("01=Back");
mode = MODE_SET_ID; // دوباره بمون در همین حالت
return;
}
// ✅ اگر آزاد بود برو مرحله وارد کردن زمان
mode = MODE_SET_INTERVAL;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Time (sec):");
lcd.setCursor(0, 1);
lcd.print("01:Back");
return;
}
else if (mode == MODE_SET_INTERVAL && currentID >= 0 && tempInput != "") {
int interval = tempInput.toInt();
if (interval > 0 && currentID < MAX_ALARMS) {
if (alarms[currentID].active) {
// اگر ID قبلاً استفاده شده
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID Already Exists!");
lcd.setCursor(0, 1);
lcd.print("New ID or 01:Back");
tempInput = "";
mode = MODE_SET_ID; // برگرد به حالت وارد کردن ID
return;
}
// اگر ID آزاد بود، ذخیرهسازی انجام بشه
alarms[currentID].interval = interval * 1000;
alarms[currentID].active = true;
alarms[currentID].snoozing = false;
// ذخیرهسازی پایدار در Preferences
saveAlarmsToPrefs();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Alarm Saved!");
lcd.setCursor(0, 1);
lcd.print("00:List A/D/#");
mode = MODE_WAIT_ACTION;
tempInput = "";
return;
}
}
else if (mode == MODE_DELETE && tempInput != "") {
int idToDelete = tempInput.toInt();
if (idToDelete >= 0 && idToDelete < MAX_ALARMS) {
if (alarms[idToDelete].active) {
alarms[idToDelete].active = false;
alarms[idToDelete].snoozing = false;
// ذخیره تغییر حذف شده
saveAlarmsToPrefs();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Deleted ID ");
lcd.print(idToDelete);
lcd.setCursor(0, 1);
lcd.print("00:List 01:Back");
mode = MODE_WAIT_ACTION;
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid ID!");
lcd.setCursor(0, 1);
lcd.print("Try again:");
mode = MODE_DELETE; // بمون در حالت حذف
}
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID out of range");
lcd.setCursor(0, 1);
lcd.print("Max: ");
lcd.print(MAX_ALARMS - 1);
mode = MODE_DELETE; // بمون در حالت حذف
}
tempInput = "";
return;
}
}
}
if (mode == MODE_WAIT_ACTION && key != 'A' && key != 'D' && key != '#' && key != '0') return;
if ((mode == MODE_SET_ID || mode == MODE_SET_INTERVAL || mode == MODE_DELETE) &&
!(isDigit(key) || key == '*' || key == 'B')) return;
if (key == '#') {
systemRunning = true;
allowInput = false;
mode = MODE_RUNNING;
lcd.clear();
lcd.print("System Running");
unsigned long now = millis();
for (int i = 0; i < MAX_ALARMS; i++) {
if (alarms[i].active && alarms[i].lastTrigger == 0) {
alarms[i].lastTrigger = now;
}
}
return;
}
if (key == 'A' && mode == MODE_WAIT_ACTION) {
mode = MODE_SET_ID;
tempInput = "";
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Add ID:");
lcd.setCursor(0, 1);
lcd.print("01:Back");
return;
}
if (key == 'D' && mode == MODE_WAIT_ACTION) {
mode = MODE_DELETE;
tempInput = "";
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Delete ID:");
lcd.setCursor(0, 1);
lcd.print("01:Back");
return;
}
// مدیریت کلید B
if (key == 'B') {
// 📌 اگر آلارم فعاله → حالت انتظار (سایلنت)
if (alertActive && currentAlertID != -1) {
noTone(buzzerPin);
alertActive = false;
alarms[currentAlertID].snoozing = true;
alarms[currentAlertID].snoozeStart = millis();
currentAlertID = -1;
return;
}
// 📌 اگر در حال تنظیم هستیم → بکاسپیس
if (mode == MODE_SET_ID || mode == MODE_SET_INTERVAL || mode == MODE_DELETE) {
if (tempInput.length() > 0) {
tempInput.remove(tempInput.length() - 1); // حذف آخرین رقم
}
// فقط خط دوم پاک و دوباره چاپ بشه
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(tempInput);
return;
}
}
if (key == 'C') {
if (alertActive && currentAlertID != -1) {
noTone(buzzerPin);
alertActive = false;
alarms[currentAlertID].lastTrigger = millis();
alarms[currentAlertID].snoozing = false;
currentAlertID = -1;
}
return;
}
if (isDigit(key) && (
mode == MODE_SET_ID || mode == MODE_SET_INTERVAL || mode == MODE_DELETE || mode == MODE_WAIT_ACTION
)) {
if (tempInput == "") {
lcd.clear();
}
tempInput += key;
if (tempInput == "01") {
tempInput = "";
mode = MODE_WAIT_ACTION;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("A:Add D:Delete");
lcd.setCursor(0, 1);
lcd.print("00:List");
return;
}
if (tempInput == "00") {
tempInput = "";
showingList = true;
displayIndex = 0;
displayNextAlarm();
return;
}
lcd.setCursor(0, 1);
lcd.print(tempInput);
return;
}
}
void displayNextAlarm() {
for (int i = displayIndex; i < MAX_ALARMS; i++) {
if (alarms[i].active) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID: ");
lcd.print(i);
lcd.setCursor(0, 1);
lcd.print("Time: ");
lcd.print(alarms[i].interval / 1000);
lcd.print(" sec");
displayIndex = i + 1;
listShowTime = millis();
return;
}
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("End of List");
lcd.setCursor(0, 1);
lcd.print("Press # to exit");
displayIndex = 0;
}
void displayPreviousAlarm() {
bool found = false;
for (int i = displayIndex - 2; i >= 0; i--) {
if (alarms[i].active) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ID: ");
lcd.print(i);
lcd.setCursor(0, 1);
lcd.print("Time: ");
lcd.print(alarms[i].interval / 1000);
lcd.print(" sec");
displayIndex = i + 1;
listShowTime = millis();
found = true;
break;
}
}
if (!found) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Start of List");
lcd.setCursor(0, 1);
lcd.print("Press * to Next");
displayIndex = 0;
}
}
// وضعیت قبلی پایه CLK برای دیتکت چرخش
int lastClkState = HIGH;
unsigned long lastButtonTime = 0;
const unsigned long buttonDebounce = 200; // میلیثانیه debounce
void handleEncoder() {
// خواندن چرخش
int clkState = digitalRead(ENCODER_CLK);
int dtState = digitalRead(ENCODER_DT);
// Detect rising edge on CLK (simple method)
if (lastClkState == HIGH && clkState == LOW) {
// چرخش یکی از طرفین
if (dtState == LOW) {
// چرخش ساعتگرد -> افزایش اندیس
if (!inContactsMenu) {
// اگر در منو نیستیم، نکن کاری (یا میتونی حرکت را برای منوهای دیگر استفاده کنی)
} else {
currentContactIndex++;
if (currentContactIndex >= MAX_CONTACTS) currentContactIndex = 0;
// پیدا کن اولین مخاطب استفادهشده
int start = currentContactIndex;
while (!contacts[currentContactIndex].used) {
currentContactIndex++;
if (currentContactIndex >= MAX_CONTACTS) currentContactIndex = 0;
if (currentContactIndex == start) break;
}
showContactOnLCD(currentContactIndex);
}
} else {
// چرخش پادساعتگرد -> کاهش اندیس
if (inContactsMenu) {
if (currentContactIndex <= 0) currentContactIndex = MAX_CONTACTS - 1;
else currentContactIndex--;
int start = currentContactIndex;
while (!contacts[currentContactIndex].used) {
if (currentContactIndex <= 0) currentContactIndex = MAX_CONTACTS - 1;
else currentContactIndex--;
if (currentContactIndex == start) break;
}
showContactOnLCD(currentContactIndex);
}
}
}
lastClkState = clkState;
// خواندن کلید فشاری انکدر (فعال LOW)
// خواندن کلید فشاری انکدر (فعال LOW)
if (digitalRead(ENCODER_SW) == LOW) {
unsigned long now = millis();
if (now - lastButtonTime > buttonDebounce) {
lastButtonTime = now;
// Debounced press detected
// اگر فعلا در منوی مخاطبین نیستیم -> کلیکِ اول: ورود به منوی مخاطبین
if (!inContactsMenu) {
// ذخیره حالت فعلیِ صفحه تا بعداً بازگردانیم
savePreviousScreenState();
inContactsMenu = true;
// موقع ورود، اگر هیچ مخاطبی نیست، پیام بده و خارج شو
if (contactCount == 0) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("No Contacts");
delay(1000);
inContactsMenu = false;
// بازگرداندن صفحه قبلی (چون ما وارد منو نشدیم)
restorePreviousScreen();
} else {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Contacts: rotate");
delay(700);
// نشان بده اولین مخاطب موجود
int idx = 0;
while (idx < MAX_CONTACTS && !contacts[idx].used) idx++;
if (idx < MAX_CONTACTS) {
currentContactIndex = idx;
showContactOnLCD(currentContactIndex);
}
}
// تا رها شدن دکمه صبر کن (برای جلوگیری از ثبت مجدد)
while (digitalRead(ENCODER_SW) == LOW) delay(10);
}
else {
// ***** ما الان در منوی مخاطبین هستیم *****
// میخواهیم تشخیص دهیم فشار (رویداد کلیک دوم) کوتاه بوده یا بلند (>= LONG_PRESS_MS)
unsigned long pressStart = millis();
bool longPressDetected = false;
// منتظر رها شدن یا تا وقتی که طول فشار به آستانه برسد
while (digitalRead(ENCODER_SW) == LOW) {
if (millis() - pressStart >= LONG_PRESS_MS) {
longPressDetected = true;
break;
}
delay(10);
}
// اگر تماس در حال حاضر فعال است (callActive == true)
// اگر تماس در حال حاضر فعال است (callActive == true)
if (callActive) {
if (longPressDetected) {
// کلیک بلند در حالت تماس -> قطع تماس
int idx = callingContactIndex;
// ارسال hangup و خاموش کردن LED
sendBlynkHangup(idx);
strip.clear();
strip.show();
// پایان تماس منطقی
callActive = false;
callingContactIndex = -1;
// نمایش پیام قطع تماس برای مدتی کوتاه
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Call ended");
lcd.setCursor(0,1);
lcd.print("Index:");
lcd.print(idx);
delay(1200);
// بعد از قطع تماس، صفحه قبلی را بازگردان
inContactsMenu = false;
restorePreviousScreen();
} else {
// کلیک کوتاه در هنگام تماس -> نادیده گرفته میشود (همون رفتار قبلی)
}
// تا رها شدن دکمه صبر کن
while (digitalRead(ENCODER_SW) == LOW) delay(10);
return;
}
// اگر تماس فعال نیست (پس این کلیک دوم میتونه یا تماس برقرار کنه یا خروج از لیست)
if (longPressDetected) {
inContactsMenu = false;
strip.clear();
strip.show();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Exit contacts");
lcd.setCursor(0,1);
lcd.print("LEDs off");
delay(800);
// بازگرداندن صفحهای که قبل از ورود به contacts بود
restorePreviousScreen();
}
else {
// کلیک کوتاه در حالت منوی مخاطبین -> برقراری تماس با مخاطب انتخاب شده
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Calling...");
lcd.setCursor(0,1);
lcd.print(contacts[currentContactIndex].name);
// --- تماس برقرار شود ---
sendBlynkCall(currentContactIndex); // این تابع باید callActive=true کند
// اجازه بده پیام دیده شود
delay(1000);
// بعد از برقراری تماس، از لیست خارج شویم اما وضعیت تماس فعال را نگه میداریم
inContactsMenu = false;
}
// تا رها شدن دکمه صبر کن (خلاص شدن از حالت bounce)
while (digitalRead(ENCODER_SW) == LOW) delay(10);
}
}
}
}
void showContactOnLCD(int idx) {
if (idx < 0 || idx >= MAX_CONTACTS) return;
if (!contacts[idx].used) return;
lcd.clear();
lcd.setCursor(0,0);
lcd.print(contacts[idx].name);
// نمایش شماره یا وضعیت تماس
lcd.setCursor(0,1);
if (callActive && callingContactIndex == idx) {
lcd.print("is calling...");
} else {
lcd.print(contacts[idx].number);
}
// همزمان رنگ LED ها را مطابق رنگ مخاطب تنظیم کن
setLEDsForContact(idx);
}
void setLEDsForContact(int idx) {
if (idx < 0 || idx >= MAX_CONTACTS) return;
if (!contacts[idx].used) {
// اگر مخاطب وجود ندارد، خاموش کن
strip.clear(); // ← تغییر یافت
strip.show();
return;
}
for (int i = 0; i < NUM_LEDS; i++) {
strip.setPixelColor(i, strip.Color(contacts[idx].r, contacts[idx].g, contacts[idx].b));
}
strip.show();
}
void sendBlynkCall(int idx) {
// علامتگذاری وضعیت تماس محلی
callActive = true;
callingContactIndex = idx;
callStartTime = millis(); // ← شروع تایمر برای نمایش تماس
// روشن کردن LEDها طبق رنگ مخاطب
setLEDsForContact(idx);
// نمایش فوری روی LCD (خط اول: نام، خط دوم: وضعیت تماس فارسی)
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(contacts[idx].name);
lcd.setCursor(0, 1);
lcd.print("is calling..."); // این خطی که خواسته بودی
// اطلاع محلی برای دیباگ و ارسال به Blynk (در صورت نیاز)
Serial.print("Blynk notify: calling ");
Serial.println(contacts[idx].name);
// مثال: Blynk.notify(String("Call start: ") + contacts[idx].name);
}