TKK_E32230551/backend-jamur-update/README.md

627 lines
26 KiB
Markdown

# 🍄 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 (`ON` ↔ `OFF`).
- 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](https://supabase.com) | Database PostgreSQL utama (gratis) |
| [HiveMQ Cloud](https://www.hivemq.com/mqtt-cloud-broker/) | MQTT Broker TLS aman port 8883 (gratis) |
| [Upstash Redis](https://upstash.com) | Redis Cloud untuk antrian BullMQ (gratis) |
| [OneSignal](https://onesignal.com) | Platform Push Notification ke Aplikasi Mobile |
| Node.js >= 18 | Runtime JavaScript lokal |
---
## ⚙️ Instalasi & Konfigurasi
### 1. Clone & Install Dependencies
```bash
git clone <repository-url>
cd backend-jamur
npm install
```
### 2. Setup Environment Variables
Buat file `.env` di root folder aplikasi, lalu isi konfigurasi berikut:
```env
# 💻 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**:
```sql
-- 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
```bash
# 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](https://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:
```bash
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**:
```json
{
"claim_code": "JAMUR01",
"user_id": "uuid-user-dari-supabase-auth"
}
```
* **Respons Sukses (200)**:
```json
{
"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)**:
```json
{
"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)**:
```json
{
"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**:
```json
{
"temp_max": 31.5,
"hum_max": 85.0
}
```
* **Respons Sukses (200)**:
```json
{
"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)**:
```json
[
{
"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**:
```json
{
"label": "Siram Sore",
"cron": "0 17 * * *",
"duration_s": 45
}
```
* **Respons Sukses (210/201)**:
```json
{
"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)**:
```json
{ "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)**:
```json
{
"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):
```json
{ "duration_s": 30 }
```
* **Respons Sukses (200)**:
```json
{ "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)**:
```json
{ "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)**:
```json
{
"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**:
```json
{ "mode": "manual" }
```
* **Respons Sukses (200)**:
```json
{
"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)**:
```json
[
{
"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)**:
```json
[
{
"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)**:
```json
[
{
"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:
```json
"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"
}
```