TKK_E32230551/backend-jamur-update/src/routes/mode.js

152 lines
5.5 KiB
JavaScript

/**
* =============================================================================
* ROUTE: MODE — Kontrol Mode Operasi ESP32
* =============================================================================
* File ini mengurus pergantian mode operasi perangkat ESP32 secara remote.
*
* ESP32 mendukung 3 mode:
* - auto : Pompa menyala/mati otomatis berdasarkan pembacaan sensor SHT31
* - manual : Pompa dikendalikan sepenuhnya oleh perintah dari aplikasi
* - offline : Mode darurat saat koneksi internet terputus (logika auto, tanpa MQTT publish)
*
* Cara Kerja:
* 1. Aplikasi Flutter memanggil POST /api/mode/:deviceId
* 2. Backend memvalidasi nilai mode
* 3. Backend mengirim perintah ke ESP32 via MQTT (cmd/mode/<deviceId>)
* 4. Backend menyimpan mode baru ke kolom current_mode di tabel devices
* 5. Backend mengirim notifikasi ke user bahwa mode telah berubah
*
* Endpoint yang tersedia:
* - GET /api/mode/:deviceId → Baca mode aktif saat ini dari database
* - POST /api/mode/:deviceId → Ubah mode operasi ESP32
* =============================================================================
*/
const router = require('express').Router()
const supabase = require('../supabase/client')
const { publishMode } = require('../mqtt/mqttClient')
const { sendNotification } = require('../utils/notification')
// Daftar nilai mode yang diizinkan (whitelist)
const VALID_MODES = ['auto', 'manual', 'offline']
// Label ramah untuk notifikasi
const MODE_LABELS = {
auto: 'AUTO 🤖 — Pompa dikendalikan sensor secara otomatis',
manual: 'MANUAL 🖐️ — Pompa dikendalikan langsung dari aplikasi',
offline: 'OFFLINE 📴 — Mode darurat tanpa koneksi cloud',
}
/**
* GET /api/mode/:deviceId
* -----------------------------------------------------------------------------
* Membaca mode operasi aktif dari database untuk sebuah device.
* Biasanya dipanggil saat aplikasi Flutter dibuka untuk menampilkan
* status mode yang sedang berjalan.
*
* Params: deviceId — ID perangkat, contoh: "esp32-new-05"
*/
router.get('/:deviceId', async (req, res) => {
const { data, error } = await supabase
.from('devices')
.select('device_id, current_mode, is_online, last_seen')
.eq('device_id', req.params.deviceId)
.single()
if (error || !data) {
return res.status(404).json({ error: 'Device tidak ditemukan' })
}
res.json({
device_id: data.device_id,
current_mode: data.current_mode ?? 'auto', // Fallback ke 'auto' jika kolom masih null
is_online: data.is_online,
last_seen: data.last_seen,
})
})
/**
* POST /api/mode/:deviceId
* -----------------------------------------------------------------------------
* Mengubah mode operasi ESP32 dan menyimpan perubahan ke database.
*
* Alur eksekusi:
* 1. Validasi nilai mode (hanya "auto", "manual", "offline" yang diterima)
* 2. Cek apakah device terdaftar dan dimiliki oleh seseorang
* 3. Kirim perintah mode baru ke ESP32 via MQTT
* 4. Update kolom current_mode di tabel devices di Supabase
* 5. Kirim notifikasi OneSignal ke pemilik device
*
* Params: deviceId — ID perangkat
* Body: { mode: "auto" | "manual" | "offline" }
*/
router.post('/:deviceId', async (req, res) => {
const { deviceId } = req.params
const { mode } = req.body
// Validasi 1: Field mode wajib dikirim
if (!mode) {
return res.status(400).json({ error: 'Field "mode" wajib diisi' })
}
// Validasi 2: Nilai mode harus salah satu dari whitelist
if (!VALID_MODES.includes(mode)) {
return res.status(400).json({
error: `Mode tidak valid. Nilai yang diizinkan: ${VALID_MODES.join(', ')}`,
})
}
// Ambil info device (dibutuhkan untuk validasi keberadaan + kirim notifikasi)
const { data: device, error: deviceErr } = await supabase
.from('devices')
.select('device_id, claimed_by, current_mode')
.eq('device_id', deviceId)
.single()
if (deviceErr || !device) {
return res.status(404).json({ error: 'Device tidak ditemukan' })
}
// Kirim perintah ganti mode ke ESP32 via MQTT
// Selalu kirim, karena ESP32 mungkin sebelumnya sedang "cooldown" dan mengabaikan perintah
publishMode(deviceId, mode)
// Simpan mode baru ke database hanya jika berbeda
if (device.current_mode !== mode) {
const { error: updateErr } = await supabase
.from('devices')
.update({ current_mode: mode })
.eq('device_id', deviceId)
if (updateErr) {
return res.status(500).json({ error: 'Gagal menyimpan mode ke database' })
}
}
// Kirim notifikasi ke pemilik device hanya jika mode benar-benar berubah
if (device.claimed_by && device.current_mode !== mode) {
try {
sendNotification(
device.claimed_by,
'Mode Kumbung Berubah 🔄',
`Mode diubah ke ${MODE_LABELS[mode]}`
)
} catch (notifErr) {
// Jangan sampai gagal notifikasi membatalkan seluruh respons
console.error('[Mode] Gagal kirim notifikasi:', notifErr.message)
}
}
console.log(`[Mode] ${deviceId}${device.current_mode}${mode} (Forced MQTT Publish)`)
res.json({
message: device.current_mode === mode
? `Perintah mode ${mode} dikirim ulang ke perangkat`
: `Mode berhasil diubah ke ${mode}`,
mode,
changed: device.current_mode !== mode,
})
})
module.exports = router