627 lines
26 KiB
Markdown
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"
|
|
}
|
|
```
|