import cv2 import numpy as np from flask import Flask, request from ultralytics import YOLO import firebase_admin from firebase_admin import credentials, db import time import base64 # ── 1. Inisialisasi Firebase ────────────────────────────────────────────────── cred = credentials.Certificate("serviceAccountKey.json") firebase_admin.initialize_app(cred, { 'databaseURL': 'https://smart-donation-adff1-default-rtdb.asia-southeast1.firebasedatabase.app/' # Firebase Storage TIDAK dipakai — foto disimpan sebagai base64 di Realtime DB # Jika suatu saat upgrade ke Blaze, tambahkan: # 'storageBucket': 'smart-donation-adff1.appspot.com' }) # ── 2. Load Model YOLO ──────────────────────────────────────────────────────── model = YOLO("best.pt") app = Flask(__name__) print("Class model:", model.names) # Verifikasi nama class saat startup NOMINAL_MAP = { '1': 1000, '2': 2000, '5': 5000, '10': 10000, '20': 20000, '50': 50000, '100': 100000, } # ── 3. Helper: Simpan foto sebagai Base64 ke Realtime Database ─────────────── def simpan_foto_terakhir(img_bgr, deteksi_label): """ Encode foto hasil deteksi ke Base64 lalu simpan langsung ke Realtime DB. Tidak butuh Firebase Storage — kompatibel dengan plan Spark (gratis). Dashboard web bisa langsung baca via """ try: # Render bounding box dari YOLO results = model(img_bgr, conf=0.7, verbose=False) annotated = results[0].plot() # Encode ke JPEG bytes lalu konversi ke Base64 string # Quality 60 → ukuran ~30-60KB, cukup untuk preview dashboard _, buffer = cv2.imencode('.jpg', annotated, [cv2.IMWRITE_JPEG_QUALITY, 60]) img_base64 = base64.b64encode(buffer).decode('utf-8') foto_data = "data:image/jpeg;base64," + img_base64 # Simpan ke Realtime DB — selalu overwrite node yang sama db.reference('smart_donation_box/last_detection').set({ 'foto_url': foto_data, # Dashboard langsung pakai nilai ini di 'label': deteksi_label, 'timestamp': int(time.time() * 1000) }) print(f"📸 Foto tersimpan ke Realtime DB ({len(img_base64) // 1024} KB)") except Exception as e: print(f"⚠️ Gagal simpan foto: {e}") # ── 4. Helper: Catat transaksi donasi & update summary ─────────────────────── def update_firebase(nominal, confidence): ref_log = db.reference('smart_donation_box/donasi_log') ref_summary = db.reference('smart_donation_box/summary') # Tambah entri baru di donasi_log ref_log.push({ 'nominal': nominal, 'confidence': round(float(confidence), 2), 'timestamp': int(time.time() * 1000) }) # Update summary (total & saldo) summary = ref_summary.get() or {} old_total = summary.get('total_donasi', 0) old_pengeluaran = summary.get('total_pengeluaran', 0) new_total = old_total + nominal new_saldo = new_total - old_pengeluaran ref_summary.update({ 'total_donasi': new_total, 'donasi_hari_ini': summary.get('donasi_hari_ini', 0) + nominal, 'saldo': new_saldo }) print(f"✅ Donasi Rp {nominal:,} | Confidence {confidence:.0%} | Total Rp {new_total:,}") # ── 5. Endpoint /predict ────────────────────────────────────────────────────── @app.route('/predict', methods=['POST']) def predict(): try: # Terima raw JPEG bytes dari ESP32-CAM file = request.data nparr = np.frombuffer(file, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) if img is None: print("❌ Gagal decode gambar dari ESP32") return "Gagal decode gambar", 400 print(f"📥 Frame diterima: {len(file) // 1024} KB") # Jalankan prediksi YOLO results = model.predict(img, conf=0.7, verbose=False) detections = [] for r in results: for box in r.boxes: label = model.names[int(box.cls[0])] conf = float(box.conf[0]) nominal = NOMINAL_MAP.get(label) if nominal is not None: update_firebase(nominal, conf) detections.append(f"Rp {nominal:,}") print(f"🎯 Terdeteksi: label='{label}' → Rp {nominal:,} ({conf:.0%})") else: print(f"⚠️ Label '{label}' tidak ada di NOMINAL_MAP, dilewati") continue # Simpan foto ke DB (selalu, baik ada deteksi maupun tidak) label_str = ', '.join(detections) if detections else 'Tidak ada deteksi' simpan_foto_terakhir(img, label_str) if not detections: print("🔍 Tidak ada uang terdeteksi") return "Tidak ada objek terdeteksi", 200 print(f"🎯 Terdeteksi: {label_str}") return f"Berhasil: {label_str}", 200 except Exception as e: print(f"❌ Error di /predict: {e}") return str(e), 500 # ── 6. Health check endpoint (opsional, untuk test koneksi) ────────────────── @app.route('/ping', methods=['GET']) def ping(): return "Smart Donation Box Server OK", 200 # ── 7. Jalankan Server ──────────────────────────────────────────────────────── if __name__ == '__main__': print("=" * 50) print(" Smart Donation Box — Flask Server") print(" Listening on http://0.0.0.0:5000") print(" Endpoint: POST /predict") print("=" * 50) app.run(host='0.0.0.0', port=5000, debug=False)