TKK_E32230551/backend-jamur-update
Ferdi 628642a1f7 first commit 2026-07-20 11:10:24 +07:00
..
src first commit 2026-07-20 11:10:24 +07:00
.gitignore first commit 2026-07-20 11:10:24 +07:00
README.md first commit 2026-07-20 11:10:24 +07:00
package-lock.json first commit 2026-07-20 11:10:24 +07:00
package.json first commit 2026-07-20 11:10:24 +07:00
test-latency.js first commit 2026-07-20 11:10:24 +07:00

README.md

🍄 Backend IoT Kumbung Jamur

Backend Node.js untuk sistem monitoring dan kontrol otomatis kumbung jamur berbasis IoT. Sistem ini menghubungkan sensor SHT31 dan relay pada ESP32 ke aplikasi mobile Flutter melalui MQTT (HiveMQ Cloud), dengan penjadwalan penyiraman otomatis menggunakan BullMQ (Redis), sistem push notification anti-spam via OneSignal, serta database utama Supabase.

Aplikasi ini dirancang dengan arsitektur High Availability (Dual Server Failover) dan In-Memory Optimizations untuk memastikan keandalan sistem yang optimal dan efisiensi resource yang sangat tinggi.


🏗️ Arsitektur Sistem Terdistribusi

Sistem ini mendukung arsitektur Failover Otomatis menggunakan dua server (Primary & Backup) untuk menjamin layanan tetap berjalan meskipun server utama mengalami kendala (down).

                 ┌──────────────────────────────────────┐
                 │             Aplikasi Flutter         │
                 └──────┬────────────────────────┬──────┘
                        │
       HTTP REST API    │
   (https://...railway.app) 
                        ▼                        
           ┌────────────────────────┐
           │     PRIMARY SERVER     ├ ────────────────────
           │       (Railway)        │                    |
           └───────────┬────────────┘                    |
                       │                                 │
                 ┌─────┴─────────────────────────────────┴─────┐
                 │                                             │
                 ▼                     ▼                       ▼
            Supabase (DB)       Upstash (Redis)           HiveMQ Cloud
         ┌────────────────┐   ┌─────────────────┐     ┌──────────────────┐
         │  Tabel Utama &  │   │  Antrian Kerja  │     │   Broker MQTT    │
         │  RPC Functions │   │    (BullMQ)     │     │      (TLS)       │
         └────────────────┘   └─────────────────┘     └────────┬─────────┘
                                                               │
                                                               ▼ (mqtts://)
                                                       ┌─────────────────┐
                                                       │ ESP32 + SHT31   │
                                                       └─────────────────┘

🔄 Alur & Fitur Utama Failover (Dynamic Failover Manager)

  1. Primary Server (Railway): Berjalan secara aktif. Menangani REST API, koneksi MQTT, pemrosesan antrian BullMQ, dan pendeteksian perangkat offline.
  2. Backup Server (VPS - Standby):
    • Saat startup, background services (MQTT, Worker BullMQ, Scheduler Restore, Offline Detector) dinonaktifkan (standby).
    • Secara berkala (setiap 10 detik / FAILOVER_PING_INTERVAL_MS), server backup mengirimkan ping (HTTP GET) ke PRIMARY_SERVER_URL.
    • Jika Primary Down (Ping Gagal): Backup server langsung mengaktifkan semua background services lokal, me-resume worker BullMQ, me-restore jadwal aktif dari database, dan mengambil alih kendali sistem.
    • Jika Primary Kembali Online (Ping Sukses): Backup server secara otomatis mendegradasi diri kembali ke mode standby (disconnect MQTT, pause worker, matikan offline detector) untuk menghindari bentrokan eksekusi (race conditions).

Optimalisasi Kinerja & Efisiensi Database

Untuk mencegah pembengkakan biaya database (kuota request Supabase) dan beban broker MQTT akibat frekuensi transmisi data ESP32 yang tinggi, backend mengimplementasikan beberapa teknik optimalisasi tingkat tinggi:

1. In-Memory Cache (Threshold Cache)

  • Masalah: ESP32 mengirim data sensor secara real-time. Jika backend harus menanyakan batas threshold ke Supabase setiap kali data masuk, database akan terbebani ribuan query per menit.
  • Solusi: Nilai threshold disimpan dalam in-memory cache server dengan TTL 30 detik (CACHE_TTL_MS). Query ke Supabase hanya dilakukan saat cache kedaluwarsa atau terjadi update threshold melalui API (cache langsung di-invalidate secara instan).

2. Smart Filtering & Deadband Logging (Sensor Log Cache)

Backend menyaring log sensor sebelum disimpan ke database Supabase melalui algoritma Deadband filtering:

  • Data sensor hanya akan disimpan ke tabel sensor_logs jika memenuhi salah satu kondisi berikut:
    • Suhu berubah lebih dari 0.5°C (TEMP_DELTA).
    • Kelembapan berubah lebih dari 1.0% (HUM_DELTA).
    • Status relay berubah (ONOFF).
    • Mode operasi perangkat berubah (auto, manual, offline).
    • Interval waktu detak jantung (heartbeat) telah mencapai 5 menit (HEARTBEAT_INTERVAL_MS), bertujuan untuk menjaga kontinuitas grafik di UI.
  • Mengurangi penyimpanan database hingga 90% tanpa kehilangan data historis yang penting.

3. Online Status Throttling

  • Status keaktifan perangkat (last_seen dan is_online) di database diperbarui secara berkala maksimal 1 menit sekali (LAST_SEEN_INTERVAL_MS), mencegah spam penulisan (write-heavy) ke Supabase.

🚨 Pendeteksi Perangkat Offline & Push Notification

1. Offline Detector Job

  • Berjalan di latar belakang setiap 5 menit.
  • Memindai perangkat di database yang berstatus is_online = true namun last_seen berumur lebih dari 5 menit lalu.
  • Jika ditemukan, secara otomatis memperbarui status perangkat menjadi offline di database dan memicu push notification darurat ke pemilik perangkat.

2. Anti-Spam Push Notification (OneSignal Integration)

  • Koneksi: Integrasi langsung ke OneSignal menggunakan API REST resmi.
  • Anti-Spam Guard (Cooldown): Setiap notifikasi yang sama (misal peringatan sensor kering/panas atau status offline) memiliki cooldown time (seperti 30 menit atau 1 jam) yang dikelola di Redis / In-Memory Map agar pengguna tidak dibanjiri spam push notification.
  • Notification Stacking & Threading: Menggunakan android_group, thread_id, dan collapse_id dengan nama jamur_monitoring_group untuk mengelompokkan notifikasi secara rapi di notification tray Android & iOS (seperti gaya chat WhatsApp).

📋 Prasyarat Layanan Cloud

Pastikan Anda memiliki akun dan konfigurasi untuk layanan-layanan berikut:

Layanan Keterangan
Supabase Database PostgreSQL utama (gratis)
HiveMQ Cloud MQTT Broker TLS aman port 8883 (gratis)
Upstash Redis Redis Cloud untuk antrian BullMQ (gratis)
OneSignal Platform Push Notification ke Aplikasi Mobile
Node.js >= 18 Runtime JavaScript lokal

⚙️ Instalasi & Konfigurasi

1. Clone & Install Dependencies

git clone <repository-url>
cd backend-jamur
npm install

2. Setup Environment Variables

Buat file .env di root folder aplikasi, lalu isi konfigurasi berikut:

# 💻 Server Configuration
PORT=3000
IS_BACKUP_SERVER=false                  # Set 'true' jika ini dideploy ke server VPS Backup
PRIMARY_SERVER_URL=https://nama-project-kamu.up.railway.app # URL Primary Server (diperlukan jika ini Backup Server)
FAILOVER_PING_INTERVAL_MS=10000         # Interval cek primary server (dalam milidetik, default 10 detik)

# ⚡ Supabase Configuration (Ambil dari: Project Settings > API)
SUPABASE_URL=https://xxxxxxxxxxxxxxxx.supabase.co
SUPABASE_SERVICE_KEY=sb_secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# 📡 HiveMQ Cloud MQTT Broker (Ambil dari: Clusters > MQTT Credentials & Connection Settings)
MQTT_BROKER_URL=mqtts://xxxxxxxxxxxxxxxxxxxxxxxx.s1.eu.hivemq.cloud:8883
MQTT_PORT=8883
MQTT_USERNAME=username_hivemq_kamu
MQTT_PASSWORD=password_hivemq_kamu

# 🔴 Upstash Redis Connection (Ambil dari: Database > Details > Connection URL)
REDIS_URL=rediss://default:xxxxxxxx@xxxx.upstash.io:6379

# 🔔 OneSignal Push Notification (Ambil dari: Settings > Keys & IDs)
ONESIGNAL_APP_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ONESIGNAL_REST_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

⚠️ PENTING: Jangan pernah melakukan commit file .env ke Git! File ini sudah otomatis diabaikan di file .gitignore.

3. Setup Database Supabase (Skema Tabel & Stored Procedure)

Jalankan perintah SQL berikut di dashboard Supabase > SQL Editor:

-- 1. TABEL UTAMA: Perangkat ESP32
CREATE TABLE devices (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    device_id TEXT UNIQUE NOT NULL,       -- e.g. "esp32-01"
    label TEXT,                           -- nama display e.g. "Kumbung Barat"
    location TEXT,                        -- lokasi penempatan
    claim_code TEXT UNIQUE,               -- kode klaim huruf kapital, e.g. "JAMUR01"
    claimed_by UUID REFERENCES auth.users(id),
    claimed_at TIMESTAMPTZ,
    is_online BOOLEAN DEFAULT false,
    last_seen TIMESTAMPTZ,
    current_mode TEXT DEFAULT 'auto'      -- mode kerja aktif: auto, manual, offline
);

-- 2. TABEL LOG: Riwayat Sensor & Log Aksi
CREATE TABLE sensor_logs (
    id BIGSERIAL PRIMARY KEY,
    device_id TEXT NOT NULL,
    temperature FLOAT,
    humidity FLOAT,
    relay_state BOOLEAN DEFAULT false,    -- true = ON, false = OFF
    mode TEXT DEFAULT 'auto',             -- snapshot mode kerja saat log terekam
    event TEXT,                           -- event penting (e.g. "manual_stop", "system_on")
    note TEXT,                            -- catatan tambahan
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 3. TABEL CONFIG: Batas Sensor Otomatisasi
CREATE TABLE thresholds (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    device_id TEXT UNIQUE NOT NULL,
    temp_max FLOAT NOT NULL DEFAULT 30.0,    -- suhu maks sebelum pompa ON
    hum_max FLOAT NOT NULL DEFAULT 80.0,     -- kelembapan maks sebelum pompa ON
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 4. TABEL JADWAL: Jadwal Penyiraman BullMQ
CREATE TABLE schedules (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    device_id TEXT NOT NULL,
    label TEXT,
    cron TEXT NOT NULL,          -- cron format: "menit jam hari bulan hari-minggu"
    duration_s INTEGER NOT NULL, -- durasi penyiraman dalam detik
    bull_job_id TEXT,            -- ID job BullMQ untuk kontrol sinkronisasi
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5. FUNCTION: Rata-rata Harian (RPC Function)
CREATE OR REPLACE FUNCTION get_daily_average(p_device_id TEXT, p_days INT)
RETURNS TABLE(day DATE, avg_temp NUMERIC, avg_hum NUMERIC) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        created_at::DATE AS day,
        ROUND(AVG(temperature)::NUMERIC, 1) AS avg_temp,
        ROUND(AVG(humidity)::NUMERIC, 1) AS avg_hum
    FROM sensor_logs
    WHERE device_id = p_device_id
      AND created_at >= NOW() - (p_days || ' days')::INTERVAL
      AND temperature IS NOT NULL
      AND humidity IS NOT NULL
    GROUP BY created_at::DATE
    ORDER BY day DESC;
END;
$$ LANGUAGE plpgsql;

-- 6. FUNCTION: Rata-rata Per Jam (RPC Function)
CREATE OR REPLACE FUNCTION get_hourly_average(p_device_id TEXT, p_days INT)
RETURNS TABLE(hour TIMESTAMPTZ, avg_temp NUMERIC, avg_hum NUMERIC) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        date_trunc('hour', created_at) AS hour,
        ROUND(AVG(temperature)::NUMERIC, 1) AS avg_temp,
        ROUND(AVG(humidity)::NUMERIC, 1) AS avg_hum
    FROM sensor_logs
    WHERE device_id = p_device_id
      AND created_at >= NOW() - (p_days || ' days')::INTERVAL
      AND temperature IS NOT NULL
      AND humidity IS NOT NULL
    GROUP BY date_trunc('hour', created_at)
    ORDER BY hour DESC;
END;
$$ LANGUAGE plpgsql;

4. Jalankan Server Secara Lokal

# Jalankan mode Development (Auto-restart via nodemon)
npm run dev

# Jalankan mode Production
npm start

Server akan aktif pada port http://localhost:3000 (atau sesuai konfigurasi env PORT).


🚀 Panduan Deployment

A. Deploy ke Railway (Sebagai Primary Server)

  1. Hubungkan repository GitHub Anda ke Railway.app.
  2. Buat Project Baru dan pilih Deploy dari GitHub.
  3. Di tab Variables, masukkan semua Environment Variables seperti isi file .env di atas (kecuali PORT karena dikelola otomatis oleh Railway).
  4. Klik Generate Domain di tab Settings > Networking untuk mendapatkan URL server publik (contoh: https://nama-project-kamu.up.railway.app).

B. Deploy ke VPS (Sebagai Backup Server)

  1. Siapkan server VPS (Ubuntu/Debian) dengan Node.js >= 18 dan PM2 terinstall.
  2. Clone repository, jalankan npm install.
  3. Set file .env dengan variabel IS_BACKUP_SERVER=true dan isikan PRIMARY_SERVER_URL dengan URL Railway yang didapatkan dari langkah di atas.
  4. Jalankan server menggunakan PM2 agar berjalan di latar belakang:
    pm2 start src/index.js --name backend-jamur-backup
    pm2 save
    pm2 startup
    

🔌 API Reference

Base URL (Development): http://localhost:3000/api
Base URL (Production): https://nama-project-kamu.up.railway.app/api

🛡️ Rate Limiting:

  • Global rate limit untuk semua API: 100 request/menit per IP.
  • Rate limit ketat untuk trigger siram manual: 5 request/menit per IP (menghindari banjir air pada kumbung).

📋 Ringkasan Daftar API (API Cheat Sheet)

Untuk mempermudah pencarian, berikut adalah ringkasan seluruh endpoint API yang tersedia pada backend ini:

Kategori Fitur / Kegunaan Method Endpoint Parameter Deskripsi Singkat
📱 Device Klaim Device Baru (Pairing) POST /api/device/claim claim_code (Body), user_id (Body) Menghubungkan perangkat fisik ke akun user via kode klaim.
Ambil Info Device User GET /api/device/my-device/:userId userId (Path) Mengambil detail perangkat milik user (status online, last seen).
Cek Status Online Device GET /api/device/status/:deviceId deviceId (Path) Membaca status keaktifan perangkat secara real-time.
🌡️ Threshold Ambil Threshold Aktif GET /api/threshold/:deviceId deviceId (Path) Membaca batas suhu & kelembapan otomatisasi aktif.
Update Threshold POST /api/threshold/:deviceId deviceId (Path), temp_max (Body), hum_max (Body), temp_min (Body, opsional) Mengubah batas threshold (langsung sinkron ke ESP32 via MQTT).
🗓️ Jadwal Ambil Semua Jadwal GET /api/schedule/:deviceId deviceId (Path) Membaca seluruh daftar jadwal penyiraman perangkat.
Buat Jadwal Baru POST /api/schedule/:deviceId deviceId (Path), cron (Body), duration_s (Body), label (Body, opsional) Mendaftarkan jadwal berulang baru ke DB & BullMQ (cron).
Hapus Jadwal DELETE /api/schedule/:id id (Path) Menghapus jadwal permanen dari database dan antrian BullMQ.
Toggle Status Jadwal PATCH /api/schedule/:id/toggle id (Path) Mengaktifkan/menonaktifkan jadwal sementara tanpa menghapus.
Trigger Siram Instan POST /api/schedule/:deviceId/now deviceId (Path), duration_s (Body, opsional) Memicu penyiraman manual sekali jalan (default 30 detik).
Hentikan Pompa Paksa POST /api/schedule/:deviceId/stop deviceId (Path) Mengirim sinyal OFF langsung ke relay pompa via MQTT.
⚙️ Mode Ambil Mode Kerja Aktif GET /api/mode/:deviceId deviceId (Path) Membaca mode kerja aktif perangkat (auto, manual, offline).
Ubah Mode Kerja POST /api/mode/:deviceId deviceId (Path), mode (Body) Mengubah mode kerja ESP32 dan sinkronisasi perintah via MQTT.
📊 Riwayat Ambil Log Sensor Terbaru GET /api/history/:deviceId deviceId (Path), limit (Query, opsional) Mengambil data sensor real-time terbaru (terlimit maks 500).
Rata-rata Harian GET /api/history/:deviceId/daily deviceId (Path), days (Query, opsional) Mengambil data rata-rata suhu & kelembapan harian (tren grafik).
Rata-rata Per Jam GET /api/history/:deviceId/hourly deviceId (Path), days (Query, opsional) Mengambil data rata-rata suhu & kelembapan per jam (grafik analitis).

📱 Device Management

1. Klaim Device Baru (Pairing)

Menghubungkan kode unik perangkat fisik dengan ID akun pengguna.

  • Endpoint: POST /api/device/claim
  • Body Request:
    {
        "claim_code": "JAMUR01",
        "user_id": "uuid-user-dari-supabase-auth"
    }
    
  • Respons Sukses (200):
    {
        "message": "Device berhasil diklaim",
        "device": {
            "device_id": "esp32-01",
            "label": "Kumbung Barat",
            "location": "Sektor A"
        }
    }
    

2. Ambil Info Device Milik User

  • Endpoint: GET /api/device/my-device/:userId
  • Respons Sukses (200):
    {
        "device_id": "esp32-01",
        "label": "Kumbung Barat",
        "location": "Sektor A",
        "is_online": true,
        "last_seen": "2026-05-24T10:00:00Z"
    }
    

🌡️ Threshold Management

1. Ambil Threshold Aktif

  • Endpoint: GET /api/threshold/:deviceId
  • Respons Sukses (200):
    {
        "device_id": "esp32-01",
        "temp_max": 30.0,
        "hum_max": 80.0,
        "updated_at": "2026-05-24T09:00:00Z"
    }
    

2. Update Threshold

Memperbarui batas threshold di DB dan langsung mengirimkan pembaruan ke ESP32 secara instan via MQTT.

  • Endpoint: POST /api/threshold/:deviceId
  • Body Request:
    {
        "temp_max": 31.5,
        "hum_max": 85.0
    }
    
  • Respons Sukses (200):
    {
        "message": "Threshold diupdate",
        "data": { "device_id": "esp32-01", "temp_max": 31.5, "hum_max": 85.0 }
    }
    

🗓️ Jadwal & Kontrol Penyiraman

1. Ambil Semua Jadwal Perangkat

  • Endpoint: GET /api/schedule/:deviceId
  • Respons Sukses (200):
    [
        {
            "id": "uuid-jadwal-1",
            "device_id": "esp32-01",
            "label": "Siram Pagi Hari",
            "cron": "0 6 * * *",
            "duration_s": 60,
            "is_active": true,
            "created_at": "2026-05-24T08:00:00Z"
        }
    ]
    

2. Buat Jadwal Penyiraman Baru

Menambahkan jadwal ke DB dan mendaftarkannya sebagai antrian repeatable job di BullMQ.

  • Endpoint: POST /api/schedule/:deviceId
  • Body Request:
    {
        "label": "Siram Sore",
        "cron": "0 17 * * *",
        "duration_s": 45
    }
    
  • Respons Sukses (210/201):
    {
        "id": "uuid-jadwal-baru",
        "device_id": "esp32-01",
        "label": "Siram Sore",
        "cron": "0 17 * * *",
        "duration_s": 45,
        "is_active": true
    }
    

3. Hapus Jadwal

Menghapus permanen jadwal dari database dan membatalkan repeatable job dari BullMQ.

  • Endpoint: DELETE /api/schedule/:id
  • Respons Sukses (200):
    { "message": "Jadwal dihapus" }
    

4. Toggle Jadwal (Aktif / Nonaktif)

Menghentikan eksekusi jadwal sementara waktu di BullMQ tanpa menghapus data jadwal dari database.

  • Endpoint: PATCH /api/schedule/:id/toggle
  • Respons Sukses (200):
    {
        "message": "Jadwal dinonaktifkan",
        "data": { "id": "uuid-jadwal-1", "is_active": false }
    }
    

5. Trigger Siram Manual (Sekali Jalan)

Memicu penyiraman instan di luar jadwal reguler.

  • Endpoint: POST /api/schedule/:deviceId/now
  • Body Request (Opsional):
    { "duration_s": 30 }
    
  • Respons Sukses (200):
    { "message": "Siram manual 30s dijadwalkan" }
    

6. Hentikan Pompa Secara Paksa

Segera mengirimkan sinyal relay OFF via MQTT untuk mematikan pompa secara langsung.

  • Endpoint: POST /api/schedule/:deviceId/stop
  • Respons Sukses (200):
    { "message": "Pompa dimatikan" }
    

⚙️ Mode Operasi Perangkat

ESP32 mendukung 3 mode operasi utama:

  • auto: Pompa dikendalikan secara otomatis berdasarkan data sensor SHT31.
  • manual: Logika otomatis dimatikan, kontrol pompa sepenuhnya diatur manual lewat API/Aplikasi.
  • offline: Mode darurat jika terputus dari jaringan cloud (logika auto berjalan mandiri di hardware lokal tanpa mempublikasikan data MQTT).

1. Ambil Mode Kerja Aktif

  • Endpoint: GET /api/mode/:deviceId
  • Respons Sukses (200):
    {
        "device_id": "esp32-01",
        "current_mode": "auto",
        "is_online": true,
        "last_seen": "2026-05-24T10:00:00Z"
    }
    

2. Ubah Mode Kerja

Mengirim perintah ganti mode ke hardware via MQTT dan menyimpan perubahannya di database.

  • Endpoint: POST /api/mode/:deviceId
  • Body Request:
    { "mode": "manual" }
    
  • Respons Sukses (200):
    {
        "message": "Mode berhasil diubah ke manual",
        "mode": "manual",
        "changed": true
    }
    

📊 Riwayat Sensor & Tren Kondisi

1. Ambil Log Sensor Terbaru

Mengambil log real-time sensor terbaru (termasuk filter in-memory deadband).

  • Endpoint: GET /api/history/:deviceId?limit=100
  • Respons Sukses (200):
    [
        {
            "temperature": 28.5,
            "humidity": 82.3,
            "relay_state": false,
            "mode": "auto",
            "created_at": "2026-05-24T10:15:00Z"
        }
    ]
    

2. Ambil Rata-rata Sensor Harian (RPC get_daily_average)

Untuk visualisasi grafik jangka panjang di aplikasi Flutter.

  • Endpoint: GET /api/history/:deviceId/daily?days=7
  • Respons Sukses (200):
    [
        {
            "day": "2026-05-24",
            "avg_temp": 28.1,
            "avg_hum": 83.4
        }
    ]
    

3. Ambil Rata-rata Sensor Per Jam (RPC get_hourly_average)

Untuk visualisasi grafik analitis jangka menengah/harian.

  • Endpoint: GET /api/history/:deviceId/hourly?days=7
  • Respons Sukses (200):
    [
        {
            "hour": "2026-05-24T10:00:00.000Z",
            "avg_temp": 28.5,
            "avg_hum": 82.9
        }
    ]
    

📡 MQTT Topics

Sistem komunikasi backend dan ESP32 menggunakan protokol MQTT over TLS (mqtts) di port 8883.

Topic Arah Payload Keterangan
sensor/sht31 ESP32 → Backend {"device_id":"esp32-01","temp":28.5,"hum":82.3,"mode":"auto","relay":false} Laporan status sensor & hardware berkala
config/threshold/{deviceId} Backend → ESP32 {"temp":31.5,"hum":85.0} Sinkronisasi perubahan threshold sensor
cmd/relay/{deviceId} Backend → ESP32 "ON" atau "OFF" Perintah langsung kontrol relay pompa
cmd/mode/{deviceId} Backend → ESP32 "auto", "manual", atau "offline" Perintah langsung untuk mengubah mode operasi

🔁 Antrian Kerja (BullMQ) & Recovery Sistem

Sistem penjadwalan dikelola secara hybrid menggunakan BullMQ dan Supabase. Hal ini memecahkan masalah hilangnya repeatable job jika server mengalami restart/deployment ulang.

Alur Kerja BullMQ Worker

  1. Job repeatable terpicu sesuai jadwal cron atau instan dari trigger siram manual.
  2. Worker BullMQ mengambil job dari Redis:
    • Mengirim perintah relay ON ke ESP32 via MQTT.
    • Mengirimkan push notification "Penyiraman Dimulai" via OneSignal ke pemilik alat.
    • Menahan eksekusi (non-blocking sleep) selama durasi siram duration_s.
    • Mengirim perintah relay OFF ke ESP32 via MQTT saat durasi berakhir.

Alur Recovery (Schedule Restore)

Saat server pertama kali menyala (atau setelah failover berpindah ke aktif):

  1. Menghapus semua job antrian lama di Redis demi mencegah tumpang tindih.
  2. Membaca semua data jadwal yang aktif (is_active = true) di tabel Supabase.
  3. Mendaftarkan ulang job ke Redis menggunakan pustaka terbaru BullMQ v5+ (menggunakan format parameter cron).

📁 Struktur Folder

backend-jamur/
├── src/
│   ├── index.js              # Entry point utama, Express & rate limit setup
│   ├── jobs/
│   │   └── offlineDetector.js# Background job pemeriksa status online perangkat
│   ├── mqtt/
│   │   └── mqttClient.js     # Koneksi HiveMQ, subscriber sensor, & publisher command
│   ├── queues/
│   │   ├── irrigationQueue.js# Inisialisasi antrian BullMQ & koneksi Redis Upstash
│   │   ├── irrigationWorker.js# Worker pengeksekusi siklus pompa ON -> DELAY -> OFF
│   │   └── scheduleRestore.js# Pemulihan otomatis jadwal aktif dari database saat startup
│   ├── routes/
│   │   ├── device.js         # API endpoint klaim & info perangkat
│   │   ├── history.js        # API endpoint riwayat & agregasi sensor (daily/hourly)
│   │   ├── mode.js           # API endpoint kontrol mode kerja ESP32
│   │   ├── schedule.js       # API endpoint CRUD jadwal & trigger pompa manual
│   │   └── threshold.js      # API endpoint pembacaan & modifikasi batas sensor
│   ├── supabase/
│   │   └── client.js         # Setup Supabase JS client (Service Role authorization)
│   └── utils/
│       └── notification.js   # Pengirim OneSignal Push Notification + Cooldown redis
├── .env                      # File konfigurasi privat (lokal)
├── .gitignore                # Daftar file terabaikan dari Git
├── package.json              # Daftar pustaka dependencies & scripts npm
└── package-lock.json         # Lock file dependencies

📦 Dependensi Utama

Detail library penting yang digunakan pada proyek ini:

"dependencies": {
  "@supabase/supabase-js": "^2.101.1",
  "bullmq": "^5.73.0",
  "dotenv": "^17.4.1",
  "express": "^5.2.1",
  "express-rate-limit": "^8.3.2",
  "ioredis": "^5.10.1",
  "mqtt": "^5.15.1"
}