#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_Fingerprint.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSansBold12pt7b.h> // Font kustom
#include <Fonts/FreeSansBold9pt7b.h> // Font kustom
#include <Fonts/FreeSerif9pt7b.h> // Font kustom
#include <Fonts/FreeSans9pt7b.h> // Font kustom
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Fonts/TomThumb.h> // Menyertakan font TomThumb
#define MODEM_RX 16
#define MODEM_TX 17
#define mySerial Serial2 // gunakan untuk ESP32
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define BUZZER_PIN 5 // Pin untuk buzzer
#define LED_PIN 18 // Pin untuk buzzer
// Prefix untuk setiap kelompok pengguna
#define PREFIX_GURU 1000 // Sensor A
#define PREFIX_KELAS7 2000 // Sensor B
#define PREFIX_KELAS8 3000 // Sensor C
#define PREFIX_KELAS9 4000 // Sensor D
// Tentukan prefix yang digunakan oleh perangkat ini (Sensor B - Kelas 7)
#define CURRENT_PREFIX PREFIX_KELAS7
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Tambahkan untuk NTP Client
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 25200, 60000); // GMT+7 (25200 detik)
// Struktur data untuk menyimpan ID dan nama
struct FingerprintData {
uint16_t id;
const char* name;
};
FingerprintData userData[] = {
{1, "Admin"},
{2, "lorem"},
{3, "ipsum"},
{4, ""},
{5, ""},
{6, "Khafid Saddam"},
{7, "Putri Dwi"},
{8, "Arif Rohman"},
{9, "Asmara Dicky"},
{10, "Rizky Panca"},
{11, "Syarifal Nizar"},
{12, "Alwan Harist"},
{13, "Fadhil Ishaq"},
{14, "Nathan Arya"},
{15, "Anisah Rani"},
{16, "Syahreza Putra"},
{17, "Bagus Syahputra"},
{18, "Rohmat Adha"},
{19, "Dea Zahra"},
{20, "Dian Putri"},
{21, "Amelia Azzahra"},
{22, "Farel Yustan"},
{23, "Haikal Saputra"},
{24, "Utami Arsytasari"},
{25, "Farid Alvino"},
{26, "Kartika Nariswari"},
{27, "Alfiayah Jannah"},
{28, "Khalil Ibrahim"},
{29, "Faris Firdaus"},
{30, "Nurin Najwa"},
{31, "Revan Cipta"},
{32, "Alfin Jaya"},
{33, "Tifa Niati"},
{34, "Baqiyyatus"},
{35, "Dyaz Anggi"},
{36, "Anggi Naura"},
{37, "Khoirun Nisa"},
{38, "Bilqish Aulia"},
{39, "Ifeliza Sekar"},
{40, "Liona Aisyah"},
{41, "Rifki Anata"},
{42, "Alif Hafizh"},
{43, "Nafla Clarissa"},
{44, "Verani Zahra"},
{45, "Mazidah Putri"},
{46, "Febrianti"},
{47, "Dwi Ramadhani"},
{48, "Faina Fildzah"},
{49, "Annisha Putrie"},
{50, "Davinko Syarif"},
{51, "Aqila Ramadhani"},
{52, "Ias Ghifari"},
{53, "Afka Ardiansyah"},
{54, "Saffana Ainayyah"},
{55, "Nur Candra"},
{56, "Mara Olevia"},
{57, "Achintya Rahma"},
{58, "Burhanuddin"},
{59, "Alif Firjatullah"},
{60, "Qotharatu Salwa"},
{61, "Zaim Dziyaul"},
{62, "Berlian Auberta"},
{63, "Syaqila Larasati"},
{64, "Jasmine Zafarani"},
{65, "Roudhotul Jannah"},
{66, "Andy Firmansyah"},
{67, "Fajar Maulana"},
{68, "Noufal Fakhri"},
{69, "Rosalino Afandi"},
{70, "Khalifah Nazaruafinesta"},
{71, "Hasbi Hafyah"},
{72, "Ilham Alby"},
{73, "Hawa Nurjanah"},
{74, "Efan Syaifudin"},
{75, "Indah Nur"}
};
const int userCount = sizeof(userData) / sizeof(userData[0]); // Hitung jumlah data
// Informasi Wi-Fi
const char* ssid = "SMP MUTU OFFICE"; // Ganti dengan nama Wi-Fi
const char* password = "INDONESIA"; // Ganti dengan password Wi-Fi
const char* serverName = "http://zidcreative.com/innoiti/cust/mastercust_5/api/fingerprint.php"; // URL PHP server
// Variabel untuk menyimpan waktu absensi terakhir
unsigned long lastAttendanceTime[130] = {0}; // Menyimpan waktu absensi per ID pengguna
int lastWifiUpdate = 0;
const int wifiUpdateInterval = 5000; // Update setiap 5 detik
int dailyAttendanceCount = 0; // Variabel untuk menyimpan jumlah absen hari ini
unsigned long currentAttendanceDay = 0; // Variabel untuk menyimpan hari terakhir absens
float currentSpeedKBps = 0;
unsigned long lastSpeedTestTime = 0;
const long speedTestInterval = 10000; // Ukur kecepatan setiap 10 detik
void setup() {
Serial.begin(9600);
while (!Serial);
delay(100);
Serial.println("\n\nzidCreative Innoiti TF 1 Verifikasi");
// Koneksi Wi-Fi
Serial.print("Menghubungkan ke Wi-Fi");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi Terhubung!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Mulai komunikasi dengan sensor sidik jari
finger.begin(57600);
if (finger.verifyPassword()) {
Serial.println("Sensor sidik jari ditemukan!");
} else {
Serial.println("Sensor sidik jari tidak ditemukan :(");
while (1) { delay(1); }
}
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
display.setFont(&FreeSansBold12pt7b); // Menggunakan font kustom
display.setTextColor(WHITE);
display.setCursor(5, 20);
display.println("Innoiti");
display.setCursor(5, 45);
display.println("FT1");
display.display();
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
timeClient.begin();
}
void loop() {
measureNetworkSpeed();
// Tunggu sidik jari
uint8_t result = getFingerprintID();
if (result == FINGERPRINT_OK) {
handleAttendance(); // Tangani absensi
} else if (result == FINGERPRINT_NOTFOUND) {
displayError("Siapa Ya?");
playErrorSound();
} else {
displayTime();
}
delay(1000); // Tunggu 1 detik sebelum mencoba lagi
}
void handleAttendance() {
int userID = finger.fingerID;
unsigned long currentTime = timeClient.getEpochTime();
unsigned long currentDay = currentTime / 86400; // Hari sejak 1 Jan 1970
// Reset counter jika hari baru
if (currentDay != currentAttendanceDay) {
dailyAttendanceCount = 0;
currentAttendanceDay = currentDay;
}
// Proses absensi
dailyAttendanceCount++; // Tambah counter
lastAttendanceTime[userID] = currentDay;
displayFingerprintID();
playSuccessSound();
}
// Fungsi untuk menampilkan pesan kesalahan
void displayError(String errorMsg) {
display.clearDisplay();
display.setFont(&FreeSansBold12pt7b);
display.setTextColor(WHITE);
display.setCursor(0, 40);
display.println(errorMsg);
display.display();
}
uint8_t getFingerprintID() {
uint8_t p = finger.getImage();
if (p != FINGERPRINT_OK) {
if (p == FINGERPRINT_NOFINGER) {
Serial.println("Tidak ada jari yang terdeteksi.");
}
return p;
}
p = finger.image2Tz();
if (p != FINGERPRINT_OK) {
Serial.println("Gagal mengonversi gambar menjadi template.");
return p;
}
p = finger.fingerFastSearch();
if (p == FINGERPRINT_OK) {
Serial.print("Sidik jari ditemukan dengan ID: ");
Serial.println(finger.fingerID);
Serial.print("Confidence: ");
Serial.println(finger.confidence);
}
return p;
}
const char* getNameByID(uint16_t id) {
for (int i = 0; i < userCount; i++) {
if (userData[i].id == id) {
return userData[i].name;
}
}
return "-";
}
void displayFingerprintID() {
displayProcessing();
const char* userName = getNameByID(finger.fingerID);
uint16_t prefixed_id = CURRENT_PREFIX + finger.fingerID;
Serial.print("ID Asli: ");
Serial.print(finger.fingerID);
Serial.print(", ID dengan Prefix: ");
Serial.print(prefixed_id);
Serial.print(", Nama: ");
Serial.println(userName);
sendData(finger.fingerID, userName);
}
void sendData(uint16_t id_jari, const char* name) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi tidak terhubung! Mencoba reconnect...");
WiFi.disconnect();
WiFi.reconnect();
delay(5000);
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi gagal terhubung. Data tidak dikirim.");
displayError("WiFi Error!");
return;
}
}
HTTPClient http;
http.begin(serverName);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
// Tambahkan prefix ke ID jari sebelum dikirim
uint16_t prefixed_id = CURRENT_PREFIX + id_jari;
String postData = "id_jari=" + String(prefixed_id) + "&name=" + String(name);
int httpResponseCode = http.POST(postData);
if (httpResponseCode > 0) {
Serial.print("Data berhasil dikirim! ID: ");
Serial.print(prefixed_id);
Serial.print(", HTTP Response: ");
Serial.println(httpResponseCode);
const char* userName = getNameByID(id_jari);
displaySuccess(userName);
} else {
Serial.println("Gagal mengirim data.");
displayError("restart!");
playErrorSound();
}
http.end();
}
void playSuccessSound() {
for (int i = 0; i < 2; i++) {
digitalWrite(BUZZER_PIN, HIGH);
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
delay(100);
}
}
void playErrorSound() {
digitalWrite(BUZZER_PIN, HIGH);
digitalWrite(LED_PIN, HIGH);
delay(1000);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
}
void displayTime() {
display.clearDisplay(); // Bersihkan layar setiap loop
timeClient.update();
String formattedTime = timeClient.getFormattedTime();
// 1. Tampilkan teks "Granit Cafe" (PASTIKAN FONT DAN POSISI KONSISTEN)
display.setFont(&FreeSerif9pt7b); // Gunakan font yang sama setiap kali
display.setTextColor(WHITE); // Pastikan warna teks putih
display.setCursor(2, 14); // Koordinat HARUS tetap sama
display.println("- SMPM 7 -");
// 2. Tampilkan waktu/jam
display.setFont(&FreeSansBold12pt7b); // Font untuk waktu
display.setCursor(17, 42); // Posisi jam
display.println(timeClient.getFormattedTime());
// 3. Fungsi tambahan (indikator WiFi dan counter)
drawWifiIndicator();
drawAttendanceCounter();
display.display(); // Update layar
}
void displaySuccess(const char* message) {
display.clearDisplay();
// Animasi lingkaran loading kecil
for(int i=0; i<=360; i+=30) {
display.clearDisplay();
drawAnimatedCircle(64, 20, 10, i);
display.display();
delay(20);
}
// Gambar centang animasi
drawAnimatedCheckmark(64, 20);
// Tampilkan pesan sukses
display.setFont(&FreeSansBold9pt7b);
display.setTextColor(WHITE);
// Hitung lebar teks untuk posisi tengah
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(message, 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w)/2, 50);
display.println(message);
// Tampilkan garis pembatas dekoratif
display.drawFastHLine(10, 55, SCREEN_WIDTH-20, WHITE);
display.display();
}
void drawAnimatedCircle(int16_t x, int16_t y, int16_t r, int16_t angle) {
float rad = angle * PI / 180;
int16_t x2 = x + r * cos(rad);
int16_t y2 = y + r * sin(rad);
display.drawLine(x, y, x2, y2, WHITE);
}
void drawAnimatedCheckmark(int16_t x, int16_t y) {
// Gambar lingkaran latar belakang
display.fillCircle(x, y, 12, WHITE);
// Animasi centang bagian per bagian
for(int i=0; i<=10; i++) {
display.fillCircle(x, y, 12, WHITE);
// Bagian pertama centang (garis miring ke bawah)
int16_t x1 = x - 8;
int16_t y1 = y;
int16_t x2 = x - 2;
int16_t y2 = y + 6;
int16_t drawX2 = x1 + (x2 - x1) * i / 10;
int16_t drawY2 = y1 + (y2 - y1) * i / 10;
display.drawLine(x1, y1, drawX2, drawY2, BLACK);
// Bagian kedua centang (garis miring ke atas)
if(i > 5) {
int16_t x3 = x2;
int16_t y3 = y2;
int16_t x4 = x + 8;
int16_t y4 = y - 6;
int16_t drawX4 = x3 + (x4 - x3) * (i-5) / 5;
int16_t drawY4 = y3 + (y4 - y3) * (i-5) / 5;
display.drawLine(x3, y3, drawX4, drawY4, BLACK);
}
display.display();
delay(30);
}
}
void drawWifiIndicator() {
int rssi = WiFi.RSSI();
int bars;
if (rssi > -55) { // Sinyal sangat kuat
bars = 4;
} else if (rssi > -65) {
bars = 3;
} else if (rssi > -75) {
bars = 2;
} else if (rssi > -85) {
bars = 1;
} else { // Sinyal sangat lemah atau tidak ada
bars = 0;
}
// Gambar indikator WiFi
int x = 7;
int y = 61;
// Bersihkan area indikator sebelumnya
display.fillRect(x, y-10, 30, 12, BLACK);
// Gambar latar belakang ikon WiFi
display.drawPixel(x, y-2, WHITE);
display.drawPixel(x+1, y-2, WHITE);
display.drawPixel(x+2, y-3, WHITE);
display.drawPixel(x+3, y-3, WHITE);
display.drawPixel(x+4, y-4, WHITE);
display.drawPixel(x+5, y-4, WHITE);
// Gambar bar sesuai kekuatan sinyal
for (int i = 0; i < bars; i++) {
display.fillRect(x + 8 + (i*5), y - 2 - (i*2), 3, 2 + (i*2), WHITE);
}
}
void drawAttendanceCounter() {
display.setFont(NULL); // Gunakan font default (5x7)
display.setTextSize(1); // Ukuran standar
// Tampilkan kecepatan jaringan (kiri)
display.setCursor(42, 55);
display.print(currentSpeedKBps, 1); // 1 digit desimal
display.print("KB/s");
// Tampilkan counter absen (kanan)
display.setCursor(100, 55);
display.print("A:");
display.print(dailyAttendanceCount);
}
// ===== Fungsi baru untuk mengukur kecepatan =====
void measureNetworkSpeed() {
if (millis() - lastSpeedTestTime >= speedTestInterval) {
HTTPClient http;
const char* testUrl = "http://httpbin.org/stream-bytes/1024"; // URL test 1KB
long fileSize = 1024; // Ukuran file test dalam bytes
http.begin(testUrl);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
long startTime = millis();
WiFiClient *stream = http.getStreamPtr();
while (stream->available()) {
stream->read();
}
long duration = millis() - startTime;
if (duration > 0) {
currentSpeedKBps = (fileSize / 1024.0) / (duration / 1000.0);
Serial.print("Kecepatan: ");
Serial.print(currentSpeedKBps);
Serial.println(" KB/s");
}
}
http.end();
lastSpeedTestTime = millis();
}
}
void displayProcessing() {
display.clearDisplay();
// Animasi loading circle (minimalis)
static float angle = 0;
const int centerX = SCREEN_WIDTH/2;
const int centerY = SCREEN_HEIGHT/2 - 5;
const int radius = 12;
// Gambar lingkaran outline
display.drawCircle(centerX, centerY, radius, WHITE);
// Gambar "dot" yang berputar
int dotX = centerX + radius * cos(angle);
int dotY = centerY + radius * sin(angle);
display.fillCircle(dotX, dotY, 2, WHITE);
angle += 0.2; // Kecepatan rotasi
if(angle > 2*PI) angle = 0;
// Teks minimalis
display.setFont(&FreeSansBold9pt7b);
display.setTextColor(WHITE);
// Hitung lebar teks untuk posisi tengah
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds("Memproses", 0, 0, &x1, &y1, &w, &h);
display.setCursor((SCREEN_WIDTH - w)/2, centerY + radius + 20);
display.println("Memproses");
display.display();
}