#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
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;
};
// Daftar ID dan nama
FingerprintData userData[] = {
{1, "innoiti"},
{2, "lorem"},
{3, "ipsum"},
{4, "dolor"},
{6, "Siroj"},
{7, "Naufal"},
{8, "Shofia"},
{9, "Ryan"},
{10, "Juli"},
{11, "Zamroni"},
{12, "Ibad"},
{13, "Fani"},
{14, "Kipli"},
{15, ""},
{16, ""},
{17, ""}
};
const int userCount = sizeof(userData) / sizeof(userData[0]); // Hitung jumlah data
// Informasi Wi-Fi
const char* ssid = "KASIR"; // Ganti dengan nama Wi-Fi
const char* password = "tentukan"; // Ganti dengan password Wi-Fi
const char* serverName = "http://zidcreative.com/innoiti/cust/mastercust_3/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(); // Tampilkan animasi processing
const char* userName = getNameByID(finger.fingerID);
sendData(finger.fingerID, userName);
Serial.print("ID: ");
Serial.print(finger.fingerID);
Serial.print(", Nama: ");
Serial.println(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); // Tunggu 5 detik sebelum mencoba lagi
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");
String postData = "id_jari=" + String(id_jari) + "&name=" + String(name);
// Kirim data hanya 1 kali
int httpResponseCode = http.POST(postData);
if (httpResponseCode > 0) {
Serial.print("Data berhasil dikirim! HTTP Response: ");
Serial.println(httpResponseCode);
// Tambahkan notifikasi OLED
const char* userName = getNameByID(finger.fingerID);
displaySuccess(userName);
} else {
Serial.println("Gagal mengirim data.");
displayError("restart!");
playErrorSound();
}
http.end(); // Tutup HTTPClient
}
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("- Granit Cafe -");
// 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();
}