Hapiçe – Otomatik İlaç Makinesi
İlaçlarını almayı unutanlar için bir 'Hatırlatıcı Cihaz' tasarlıyoruz.
Merhabalar. İlaçları düzenli kullanmak önemli elbette. Bazı hastalıklar için ilaçları tam saatinde almak çok daha önemli olabiliyor. Biz de ilacını almayı unutanlar için bir cihaz tasarladık. Aslına bakarsanız bir şey deniyorum. Bildiğiniz gibi tüketici elektroniği ürünleri çok genel bir işlev sunuyor. Kendi ihtiyaçlarımız için kendi cihazlarımızı yaptığımızda ise hiçbir markanın sunamayacağı esneklikte bir makine elde ediyoruz.
Yaptığımız cihaza istediğimiz özelliği ekleyip istemediğimizi çıkarabiliyoruz ve bunu yapmak sandığınızdan daha kolay ve eğlenceli. Benim amacımsa bu bilmediğiniz dünyanın kapılarını arayıp lezzetini tatmanızı sağlamak. Kendi ellerinizle bir şey inşaa ettiğinizde ve bir kahve alıp tıkır tıkır çalışmasını izlediğinizde oluşacak tatmin hissini çok daha fazla kişiye deneyimletmeyi hedefliyorum.
Hapiçe büyük bir projenin ilk denemesidir. Bunun gibi pek çok eğlenceli cihaz tasarlayıp tüm kaynakları herkesin yapabilmesi için ücretsiz paylaşmayı hedefliyorum. Ayrıca hiç bilmeyenler veya 3D baskı imkanı olmayanlar için proje için gereken baskılar, kablolar, kartlar kısacası aklınıza gelen her şeyi tek bir kutu halinde satın alabileceğiniz bir sistem üzerinde çalışıyoruz.
Not: Tamamlandığında bu paragrafı güncelleyeceğim, paket haline getirme çalışmaları devam ediyor! (edit: geciktiğini biliyorum, şu sıralar bir tadılatın içindeyim ve ortalık per perişan, lütfen idare edin. 🥲)
“A’dan Z’ye başkasının tasarladığı bir projenin bana ne faydası var” diye düşünenlerdenseniz yanılıyor olabilirsiniz. Her ne kadar kapsamlı bir rehber sunsam da mutlaka projeyi yaparken sorunlarla karşılaşacaksınız. Belki bilgisayarınız mikrodenetleyiciyi tanımayacak, belki saatlerce yanlış bağladığınız bir kabloyu arayacaksınız. İlk bakışta bu durum olumsuz gibi görünse de aslında mantığı kavramanızın en pratik ve öğretici yolu bu. Artık sorunları çözmek için güzel bir araca sahipsiniz: AI. Bu özellikle elektronik öğrenmeye çalışanlar için büyük bir lüks.
Neyse, daha fazlasını videolarda konuşuruz. Hadi şu cihazın yapımına geçelim.
1. Kullanılan Ürünler ve Parçalar
İlk olarak şu parçaları edinmeniz gerekiyor. Alttaki liste ögelerine tıkladığınızda sistem sizi gereken parçanın satın alma sayfasına yönlendiriyor. Stokta yoksa veya başka bir yerden satın almak isterseniz bu da gayet mümkün elbette.
2. 3D Baskı Dosyaları
Şimdi sırada 3D baskı işi var. Aşağıdaki linkteki .zip dosyasında basılması gereken tüm parçalar mevcut.
👉 Hapiçe 3D Baskı Dosyalarını İndir
3. Bağlantı Şeması
Artık hazırız. Şimdi sıra bağlantıları yapmakta. Şimdiki göreviniz aşağıdaki tablolara göre kablo bağlantılarını yapmak.
Örneğin “ESP32’nin 5 numaralı pinini ekranın CS pinine bağla” ifadesi bu pinleri birbirine uygun bir jumper kablo bağlamayı ifade eder. Devre kartlarında VCC, 5V, 3.3V, VIN gibi ifadeler pillerdeki “+” kutbunu, GND ise “-” kutbunu ifade eder. Breadboard ile 1 pini çok pine çevireceğiz (üçlü priz mantığı).
⚠️ Önemli Uyarı: ESP32’nin VIN pinini ve GND pinini jumper kablo ile breadboard’a taşımalısınız. Eğer VIN ve GND pinlerini birbirine temas ettirirseniz ESP32’niz bozulabilir!
Ekran Bağlantısı
| ESP32 Pini | Ekran Pini |
|---|---|
| GND | GND |
| VIN | VCC |
| 18 | SCK |
| 23 | SDA |
| 22 | RES |
| 21 | A0 |
| 5 | CS |
| VIN | LED |
Motor Bağlantısı
| ESP32 Pini | ULN2003 |
|---|---|
| 13 | IN1 |
| 12 | IN2 |
| 14 | IN3 |
| 27 | IN4 |
| GND | - |
| VIN | + (5V) |
Buton ve Buzzer Bağlantıları
| ESP32 Pini | Buton | ESP32 Pini | Hoparlör | |
|---|---|---|---|---|
| 32 | PIN 1 | 26 | + | |
| GND | PIN 2 | GND | - |
4. Montaj ve Güç Bağlantısı
Sadece bir MicroUSB kablosuna ihtiyacınız var. ESP32’ye takacağınız bu kablo ile tüm cihazı besleyebilirsiniz. 3D baskı alt kutusunun içerisinde her parçayı monte edebilmeniz için bariz kısımlar tasarladım.
Montaj sırasında minik vidalara ihtiyacınız olacak. Ben 1000’li olarak şu linkten satın almıştım: 🔗 Sywoods Mini Vida 2.2x6.5 mm (1000 Adet)
5. Yazılım ve Kodu Yükleme
Sadece kodu yükleme kısmı kaldı. Önce ücretsiz Arduino IDE programını yüklemeniz gerekiyor: 🔗 Arduino IDE İndir
1. ESP32’yi Bilgisayara Bağlamak Micro USB kablonuzun bir ucunu ESP32’ye, diğer ucunu bilgisayarınıza takın. Kablonuzun sadece “şarj” eden değil, “veri aktarımı” yapan bir kablo olduğundan emin olun.
2. Arduino IDE’ye ESP32’yi Tanıtmak
- Arduino IDE’yi açın ve Dosya > Tercihler sekmesine tıklayın.
- “Ek Devre Kartları Yöneticisi URL’leri” kısmına şu linki yapıştırın:
https://dl.espressif.com/dl/package_esp32_index.json - Araçlar > Kart > Kart Yöneticisi bölümüne girip “ESP32” aratın ve kurun.
3. Gerekli Kütüphaneleri Yüklemek Kütüphane Yöneticisi üzerinden sırasıyla şu paketleri kurun:
UniversalTelegramBot(Brian Lough)Adafruit GFX LibraryAdafruit ST7735 and ST7789 LibraryArduinoJson(Benoit Blanchon)
4. Kodu Yapıştırmak ve Yüklemek
- Arduino IDE’deki her şeyi silin ve aşağıdaki kodu yapıştırın.
- Kodun başındaki
XXXXXXXXXXyazan kısımlara kendi WiFi, Bot Token ve Chat ID bilgilerinizi girin. (Telegram botunu nasıl kuracağınızı videodan detaylı izleyebilirsiniz). - Araçlar > Kart kısmından
DOIT ESP32 DEVKIT V1seçin, portunuzu ayarlayın ve “Yükle” okuna basın. - Ekranda “Connecting…” yazısını gördüğünüzde ESP32 üzerindeki BOOT butonuna basılı tutun. Yüzde ilerlemeye başlayınca bırakabilirsiniz.
Kolay Gelsin! 🖐️
Arduino IDE Kodu
/*
AKILLI İLAÇ DİSPANSERİ VE TAKİP SİSTEMİ
Bu kod, saatlik ilaç rutinini takip eden, hastaya görsel/işitsel uyarı veren,
motor mekanizması ile ilaç veren ve hasta yakınına Telegram üzerinden bilgi veren bir sistemdir.
*/
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <Preferences.h>
#include <Stepper.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <time.h>
// ==============================================================================
// KULLANICI AYARLARI (KODLAMA BİLMEYENLER SADECE BURAYI DOLDURACAK)
// ==============================================================================
const char* WIFI_SSID = "XXXXXXXXXX"; // WiFi Ağ Adı
const char* WIFI_PASSWORD = "XXXXXXXXXX"; // WiFi Şifresi
#define BOTtoken "XXXXXXXXXX" // Telegram Bot Token (BotFather'dan alınır)
#define CHAT_ID "XXXXXXXXXX" // Bildirim gidecek kişinin Telegram Chat ID'si
// Cihazın kullanılacağı ülkenin saat dilimi (Türkiye için GMT+3 -> 3 * 3600 = 10800)
const long TIMEZONE_OFFSET = 10800;
const int DAYLIGHT_OFFSET = 0; // Yaz saati uygulaması (Türkiye'de kalktığı için 0)
// ==============================================================================
// KULLANICI AYARLARI BİTİŞİ
// ==============================================================================
// --- PIN TANIMLAMALARI ---
#define TFT_CS 5
#define TFT_RST 22
#define TFT_DC 21
#define TFT_MOSI 23 // SDA
#define TFT_SCLK 18 // SCK
#define MOTOR_IN1 13
#define MOTOR_IN2 12
#define MOTOR_IN3 14
#define MOTOR_IN4 27
#define BUTTON_PIN 32
#define BUZZER_PIN 26
// --- MELODİ NOTA FREKANSLARI ---
#define NOTE_C5 523
#define NOTE_D5 587
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_G4 392
#define NOTE_G5 784
#define NOTE_A4 440
#define NOTE_A5 880
#define NOTE_B4 494
// Tetris Melodisi (Nota, Vuruş)
int Tetris[] = {
NOTE_E5,4, NOTE_B4,8, NOTE_C5,8, NOTE_D5,4, NOTE_C5,8, NOTE_B4,8,
NOTE_A4,4, NOTE_A4,8, NOTE_C5,8, NOTE_E5,4, NOTE_D5,8, NOTE_C5,8,
NOTE_B4,4, NOTE_B4,8, NOTE_C5,8, NOTE_D5,4, NOTE_E5,4,
NOTE_C5,4, NOTE_A4,4, NOTE_A4,2, 0,4,
NOTE_D5,4, NOTE_F5,8, NOTE_A5,4, NOTE_G5,8, NOTE_F5,8,
NOTE_E5,4, NOTE_C5,8, NOTE_E5,4, NOTE_D5,8, NOTE_C5,8,
NOTE_B4,4, NOTE_B4,8, NOTE_C5,8, NOTE_D5,4, NOTE_E5,4,
NOTE_C5,4, NOTE_A4,4, NOTE_A4,2, 0,4
};
// Başarı Melodisi
int basari[] = { NOTE_G4, 12, NOTE_C5, 12, NOTE_E5, 12, NOTE_G5, 4, NOTE_E5, 12, NOTE_G5, 2 };
// --- NESNELER ---
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
Stepper stepper(2048, MOTOR_IN1, MOTOR_IN3, MOTOR_IN2, MOTOR_IN4);
WiFiClientSecure client;
UniversalTelegramBot bot(BOTtoken, client);
Preferences preferences;
// --- GLOBAL DEĞİŞKENLER (Çift Çekirdek uyumlu olması için volatile eklendi) ---
volatile unsigned long intervalSeconds = 0; // Artık saniye bazlı tutuyoruz (00:01 desteği için)
volatile unsigned long lastPillTime = 0;
volatile bool alarmActive = false;
volatile bool buzzerMuted = false;
volatile unsigned long alarmStartTime = 0;
volatile bool alert5MinSent = false;
volatile bool alert15MinSent = false;
volatile bool weeklyReportSent = false;
volatile bool forceScreenRefresh = false;
// Puanlama Sistemi
int totalDosesThisWeek = 0;
int onTimeDosesThisWeek = 0;
// Ekran tazeleme için
int sonSaat = -1;
int sonDakika = -1;
int sonSaniye = -1;
bool isFlashing = false;
// Kesme (Interrupt) için bayrak
volatile bool buttonPressed = false;
// --- TELEGRAM MESAJ KUYRUĞU (Çekirdekler arası iletişim) ---
#define MAX_MSG_QUEUE 10
String msgQueue[MAX_MSG_QUEUE];
volatile int queueHead = 0;
volatile int queueTail = 0;
void queueTelegramMessage(String msg) {
int nextHead = (queueHead + 1) % MAX_MSG_QUEUE;
if (nextHead != queueTail) { // Kuyruk dolu değilse
msgQueue[queueHead] = msg;
queueHead = nextHead;
}
}
volatile unsigned long lastInterruptTime = 0;
void IRAM_ATTR handleButton() {
unsigned long interruptTime = millis();
// Buton sekmesini (bouncing) önlemek için 200 milisaniye koruma
if (interruptTime - lastInterruptTime > 200) {
buttonPressed = true;
}
lastInterruptTime = interruptTime;
}
// ==========================================
// TELEGRAM GÖREVİ (CORE 0 - Arka Plan)
// ==========================================
void telegramTaskCode(void * pvParameters) {
for(;;) {
// 1. Ana döngüden gelen (kuyruktaki) mesajları gönder
while (queueTail != queueHead) {
String msg = msgQueue[queueTail];
bot.sendMessage(CHAT_ID, msg, "");
queueTail = (queueTail + 1) % MAX_MSG_QUEUE;
}
// 2. Yeni mesajları kontrol et
int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
while (numNewMessages) {
handleTelegramMessages(numNewMessages);
numNewMessages = bot.getUpdates(bot.last_message_received + 1);
}
// İşlemciyi ve WiFi'yi boğmamak için bekle
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(BUTTON_PIN, handleButton, FALLING);
stepper.setSpeed(15);
tft.initR(INITR_144GREENTAB);
tft.setRotation(2);
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 10);
if (String(WIFI_SSID) == "XXXXXXXXXX" || String(BOTtoken) == "XXXXXXXXXX") {
tft.setTextColor(ST77XX_RED);
tft.println("HATA!");
tft.println("Lutfen kod icindeki");
tft.println("WIFI, BOT_TOKEN ve");
tft.println("CHAT_ID ayarlarini doldurun!");
while (true);
}
// NVS Başlat
preferences.begin("ilac_app", false);
intervalSeconds = preferences.getULong("intSec", 0); // Eski "interval" yerine intSec kullanıldı
lastPillTime = preferences.getULong("lastPill", 0);
totalDosesThisWeek = preferences.getInt("totDose", 0);
onTimeDosesThisWeek = preferences.getInt("onTDose", 0);
tft.println("Wifi Araniyor...");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
client.setInsecure();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
tft.print(".");
}
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(10, 10);
tft.setTextColor(ST77XX_GREEN);
tft.println("Baglanti Basarili!");
tft.setTextColor(ST77XX_WHITE);
tft.println("Saat bilgisi aliniyor...");
configTime(TIMEZONE_OFFSET, DAYLIGHT_OFFSET, "pool.ntp.org", "time.nist.gov");
struct tm timeinfo;
while (!getLocalTime(&timeinfo)) {
Serial.println("NTP bekleniyor...");
delay(1000);
}
tft.println("Saat guncellendi.");
String baslangicMesaji = "🤖 Sistem Başlatıldı!\n";
if (intervalSeconds == 0) {
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(10, 50);
tft.setTextColor(ST77XX_YELLOW);
tft.println("Lutfen ilacin");
tft.println("araligini");
tft.println("Telegram'dan giriniz.");
baslangicMesaji += "⚠️ Lütfen ilacın periyodunu giriniz. (Örn: 'aralık 8' veya 'aralık 00:01')";
} else {
baslangicMesaji += "✅ Süre aralığı hafızadan yüklendi.\nCihaz hazır.";
tft.fillScreen(ST77XX_BLACK);
sonSaat = -1; sonDakika = -1; sonSaniye = -1;
}
queueTelegramMessage(baslangicMesaji);
// ESP32'nin Core 0'ında Telegram Görevini Başlat (Bu sayede ekran DONMAYACAK)
xTaskCreatePinnedToCore(
telegramTaskCode,
"TelegramTask",
10000,
NULL,
1,
NULL,
0);
}
void loop() {
unsigned long currentUnixTime = getCurrentUnixTime();
// Telegram üzerinden yeni veri girildiyse ekranı tazelemek için bayrak
if (forceScreenRefresh) {
tft.fillScreen(ST77XX_BLACK);
sonSaat = -1; sonDakika = -1; sonSaniye = -1;
forceScreenRefresh = false;
}
checkWeeklyReport();
if (intervalSeconds == 0) return; // Ayar yapılana kadar beklet
// SAYAÇ VE ALARM MANTIĞI
long secondsRemaining = (lastPillTime + intervalSeconds) - currentUnixTime;
if (secondsRemaining <= 0) {
// ALARM DURUMU
if (!alarmActive) {
alarmActive = true;
alarmStartTime = currentUnixTime;
buzzerMuted = false;
alert5MinSent = false;
alert15MinSent = false;
queueTelegramMessage("🚨 İlaç zamanı geldi! Alarm veriliyor."); // Bloklamadan gönder
tft.fillScreen(ST77XX_BLACK);
sonSaat = -1; sonDakika = -1; sonSaniye = -1;
}
long secondsLate = abs(secondsRemaining);
ekraniCiz(secondsLate, true);
if (secondsLate >= 300 && !alert5MinSent) {
queueTelegramMessage("⚠️ Alarm 5 dakikadır çalıyor, hasta hala ilacını almadı!");
alert5MinSent = true;
}
if (secondsLate >= 900 && !alert15MinSent) {
queueTelegramMessage("🆘 15 dakika oldu, lütfen hastaya ulaşınız!");
alert15MinSent = true;
}
if (!buzzerMuted) {
playTetrisNonBlocking();
}
} else {
// NORMAL GERİ SAYIM
alarmActive = false;
ekraniCiz(secondsRemaining, false);
}
// BUTON KONTROLÜ
if (buttonPressed) {
buttonPressed = false;
// SADECE alarm aktifse (zamanı geldiyse) ilaç ver
if (alarmActive) {
ilacVer(currentUnixTime);
}
// Zamanı gelmeden basılırsa sistem hiçbir şey yapmadan görmezden gelir.
}
}
// ==========================================
// YARDIMCI FONKSİYONLAR
// ==========================================
void handleTelegramMessages(int numNewMessages) {
for (int i = 0; i < numNewMessages; i++) {
String chat_id = String(bot.messages[i].chat_id);
if (chat_id != CHAT_ID) continue;
String text = bot.messages[i].text;
text.toLowerCase();
// Aralık algılama (Hem "aralık 8" hem "aralık 00:01" formatını destekler)
if (text.startsWith("aralık ")) {
String param = text.substring(7);
param.trim();
unsigned long yeniSaniye = 0;
// HH:MM format kontrolü
if (param.indexOf(':') != -1) {
int separatorIndex = param.indexOf(':');
int h = param.substring(0, separatorIndex).toInt();
int m = param.substring(separatorIndex + 1).toInt();
yeniSaniye = (h * 3600) + (m * 60);
} else {
// Sadece saat formatı
yeniSaniye = param.toInt() * 3600;
}
if (yeniSaniye > 0) {
intervalSeconds = yeniSaniye;
preferences.putULong("intSec", intervalSeconds);
if (lastPillTime == 0) {
lastPillTime = getCurrentUnixTime();
preferences.putULong("lastPill", lastPillTime);
}
forceScreenRefresh = true;
bot.sendMessage(chat_id, "✅ Aralık ayarlandı. (" + param + ")", "");
} else {
bot.sendMessage(chat_id, "❌ Hatalı değer! Örnek: 'aralık 8' veya 'aralık 00:01'", "");
}
}
else if (text == "ilaç alındı" || text == "ilac alindi") {
bot.sendMessage(chat_id, "✅ Komut alındı. Sayım sıfırlanıyor (Motor dönmeyecek).", "");
resetTimer(getCurrentUnixTime());
}
else if (text == "sustur") {
if (alarmActive) {
buzzerMuted = true;
noTone(BUZZER_PIN);
bot.sendMessage(chat_id, "🔇 Alarm susturuldu. Ekran yanıp sönmeye devam edecek.", "");
} else {
bot.sendMessage(chat_id, "Cihaz şu an zaten alarm durumunda değil.", "");
}
}
else {
bot.sendMessage(chat_id, "Komutlar:\n- aralık [saat] veya [saat:dakika] (Örn: aralık 8 veya aralık 00:01)\n- ilaç alındı\n- sustur", "");
}
}
}
void ilacVer(unsigned long currentUnixTime) {
// 1. Gecikme süresini hesapla (Gerçek planlanan zaman ile şu anki zaman arası fark)
long expectedPillTime = lastPillTime + intervalSeconds;
long delaySecs = currentUnixTime - expectedPillTime;
if (delaySecs < 0) delaySecs = 0; // Hata payı tedbiri
int dHours = delaySecs / 3600;
int dMins = (delaySecs / 60) % 60;
int dSecs = delaySecs % 60;
// Saniye verisini dakikaya yuvarla (30 saniye ve üzeri ise 1 dakika ekler)
if (dSecs >= 30) {
dMins++;
if (dMins >= 60) {
dMins = 0;
dHours++;
}
}
String gecikmeStr = "";
if (dHours > 0) {
gecikmeStr = String(dHours) + " saat " + String(dMins) + " dakika";
} else {
gecikmeStr = String(dMins) + " dakika";
}
// Puanlama Kontrolü
totalDosesThisWeek++;
if (delaySecs <= 900) { // İlk 15 dakika içinde alındıysa zamanında say
onTimeDosesThisWeek++;
}
preferences.putInt("totDose", totalDosesThisWeek);
preferences.putInt("onTDose", onTimeDosesThisWeek);
noTone(BUZZER_PIN);
// 2. ARA EKRAN: Gecikme Süresi (2 Saniye Bekler)
tft.fillScreen(ST77XX_BLACK);
tft.setFont(); // Standart okunabilir fonta dön
tft.setTextSize(2);
tft.setTextColor(ST77XX_YELLOW);
tft.setCursor(16, 20);
tft.print("Gecikme:");
tft.setTextColor(ST77XX_WHITE);
if (dHours > 0) {
tft.setCursor(20, 50);
tft.print(String(dHours) + " saat");
tft.setCursor(20, 80);
tft.print(String(dMins) + " dk");
} else {
tft.setCursor(10, 60);
tft.print(String(dMins) + " dakika");
}
// Telegram Bildirimi (Kuyruğa atılır, donma yapmaz)
queueTelegramMessage("💊 Hasta İlacını Aldı! Gecikme: " + gecikmeStr);
delay(2000); // Ekrandaki gecikme yazısının okunması için 2 saniye bekle
// 3. İLAÇ VERİLİYOR EKRANI
tft.fillScreen(ST77XX_BLACK);
tft.setFont(); // Standart font
tft.setTextSize(2);
tft.setTextColor(ST77XX_GREEN);
tft.setCursor(16, 40);
tft.print("Ilaciniz");
tft.setCursor(10, 70);
tft.print("veriliyor");
// 4. Motoru Döndür
stepper.step(147);
digitalWrite(MOTOR_IN1, LOW);
digitalWrite(MOTOR_IN2, LOW);
digitalWrite(MOTOR_IN3, LOW);
digitalWrite(MOTOR_IN4, LOW);
playSuccessMelody();
resetTimer(currentUnixTime);
// İlaç verilirken gerçekleşmiş olabilecek tüm buton sekmelerini iptal et
buttonPressed = false;
}
void resetTimer(unsigned long currentUnixTime) {
lastPillTime = currentUnixTime;
preferences.putULong("lastPill", lastPillTime);
alarmActive = false;
buzzerMuted = false;
alert5MinSent = false;
alert15MinSent = false;
forceScreenRefresh = true;
}
void playTetrisNonBlocking() {
static int noteIndex = 0;
static unsigned long previousMillis = 0;
static bool isRest = false;
static int currentDuration = 0;
int totalNotes = sizeof(Tetris) / sizeof(Tetris[0]) / 2;
int tempo = 144;
int wholenote = (60000 * 4) / tempo;
// O anki nota veya boşluk süresi doldu mu kontrol et
if (millis() - previousMillis >= currentDuration) {
previousMillis = millis(); // Zamanı sıfırla
if (!isRest) {
if (noteIndex >= totalNotes) noteIndex = 0;
int noteVal = Tetris[noteIndex * 2];
int divider = Tetris[(noteIndex * 2) + 1];
if (divider > 0) {
currentDuration = (wholenote) / divider;
} else if (divider < 0) {
currentDuration = (wholenote) / abs(divider);
currentDuration *= 1.5;
}
if (noteVal != 0) tone(BUZZER_PIN, noteVal, currentDuration * 0.9);
else noTone(BUZZER_PIN);
isRest = true; // Sırada kısa bir boşluk var
} else {
noTone(BUZZER_PIN);
currentDuration = 20; // Notalar arası staccato (kesik çalma) için 20ms boşluk
isRest = false;
noteIndex++;
}
}
}
void playSuccessMelody() {
int tempo = 120;
int wholenote = (60000 * 4) / tempo;
for (int i = 0; i < sizeof(basari) / sizeof(basari[0]) / 2; i++) {
int divider = basari[i * 2 + 1];
int noteDuration = (wholenote) / divider;
tone(BUZZER_PIN, basari[i * 2], noteDuration * 0.9);
delay(noteDuration);
noTone(BUZZER_PIN);
}
}
void ekraniCiz(long saniyeVerisi, bool isAlarm) {
int saat = (saniyeVerisi / 3600);
int dakika = (saniyeVerisi / 60) % 60;
int saniye = saniyeVerisi % 60;
char metinKutusu[15];
if (isAlarm) {
// ALARM EKRANI TASARIMI (Rengarenk + İLAÇ ZAMANI + 00:00:00)
uint16_t renkler[] = {ST77XX_RED, ST77XX_BLUE, ST77XX_MAGENTA, ST77XX_YELLOW, ST77XX_GREEN};
int renkIndeksi = (millis() / 300) % 5; // Her 300ms'de bir renk değişir
uint16_t bgColor = renkler[renkIndeksi];
static int sonRenkIndeksi = -1;
// Sadece renk değiştiğinde veya saniye arttığında ekranı tazele (Titremeyi engeller)
if (sonRenkIndeksi != renkIndeksi || saniye != sonSaniye || !isFlashing) {
tft.fillScreen(bgColor);
tft.setFont(); // Standart temiz pixel fonta geç
tft.setTextSize(2);
tft.setTextColor(ST77XX_BLACK); // <-- YAZI RENGİ SİYAH OLARAK GÜNCELLENDİ
// Ekrana ortalamak için manuel koordinatlar (128x128 ekran için)
tft.setCursor(40, 15);
tft.print("ILAC");
tft.setCursor(28, 35);
tft.print("ZAMANI");
// Gecikme etiketi
tft.setTextSize(1);
tft.setCursor(43, 70);
tft.print("Gecikme");
// 00:00:00 Gecikme sayacı
sprintf(metinKutusu, "%02d:%02d:%02d", saat, dakika, saniye);
tft.setTextSize(2);
tft.setCursor(16, 85); // Tam ortaya hizalandı
tft.print(metinKutusu);
sonRenkIndeksi = renkIndeksi;
sonSaat = saat;
sonDakika = dakika;
sonSaniye = saniye;
isFlashing = true; // Alarm döngüsünde olduğumuzu işaretle
}
} else {
// NORMAL GERİ SAYIM EKRANI TASARIMI
if (isFlashing) {
tft.fillScreen(ST77XX_BLACK); // Alarm bittiyse ekranı siyah yap
sonSaat = -1; sonDakika = -1; sonSaniye = -1;
isFlashing = false;
}
uint16_t bgColor = ST77XX_BLACK;
if (saat != sonSaat) {
sprintf(metinKutusu, "%02d", saat);
tft.fillRect(25, 10, 60, 32, bgColor);
tft.setFont(&FreeSansBold18pt7b);
tft.setTextSize(1);
tft.setCursor(30, 38);
tft.setTextColor(ST77XX_CYAN);
tft.print(metinKutusu);
tft.setFont();
tft.setTextSize(1);
tft.setCursor(90, 25);
tft.setTextColor(ST77XX_WHITE);
tft.print("SAAT");
sonSaat = saat;
}
if (dakika != sonDakika) {
sprintf(metinKutusu, "%02d", dakika);
tft.fillRect(25, 50, 60, 32, bgColor);
tft.setFont(&FreeSansBold18pt7b);
tft.setCursor(30, 78);
tft.setTextColor(ST77XX_GREEN);
tft.print(metinKutusu);
tft.setFont();
tft.setCursor(90, 65);
tft.setTextColor(ST77XX_WHITE);
tft.print("DAK");
sonDakika = dakika;
}
if (saniye != sonSaniye) {
sprintf(metinKutusu, "%02d", saniye);
tft.fillRect(25, 90, 60, 32, bgColor);
tft.setFont(&FreeSansBold18pt7b);
tft.setCursor(30, 118);
tft.setTextColor(ST77XX_YELLOW);
tft.print(metinKutusu);
tft.setFont();
tft.setCursor(90, 105);
tft.setTextColor(ST77XX_WHITE);
tft.print("SAN");
sonSaniye = saniye;
}
}
}
unsigned long getCurrentUnixTime() {
time_t now;
time(&now);
return now;
}
void checkWeeklyReport() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) return;
if (timeinfo.tm_wday == 0 && timeinfo.tm_hour == 21 && timeinfo.tm_min == 0) {
if (!weeklyReportSent) {
int score = 100;
if (totalDosesThisWeek > 0) {
score = (onTimeDosesThisWeek * 100) / totalDosesThisWeek;
}
String report = "📊 HAFTALIK İLAÇ RAPORU 📊\n\n";
report += "Bu hafta alınması gereken: " + String(totalDosesThisWeek) + "\n";
report += "Zamanında alınan: " + String(onTimeDosesThisWeek) + "\n";
report += "\n💯 HAFTALIK PUAN: " + String(score) + "/100\n";
if(score == 100) report += "Mükemmel! Bütün ilaçlar zamanında alındı.";
else if(score > 70) report += "İyi ama biraz daha dikkat edilebilir.";
else report += "Lütfen hastanın ilaç rutinini sıkı takibe alınız!";
queueTelegramMessage(report);
totalDosesThisWeek = 0;
onTimeDosesThisWeek = 0;
preferences.putInt("totDose", 0);
preferences.putInt("onTDose", 0);
weeklyReportSent = true;
}
} else {
weeklyReportSent = false;
}
}
