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)