Deploy Monitor Smart Home: Dari Fly.io ke Server Sendiri
Cara bikin monitor smart home pake Python, Tuya, Redis, MongoDB, dan Telegram. Terus migrasi dari Fly.io ke server sendiri biar nol biaya bulanan.

Tagihan listrik gue terus naik tiap bulan. Gue curiga pompa air di rumah nyala lebih lama dari yang harusnya. Kadang nyala di jam-jam aneh dan gue sama sekali nggak tau udah berapa lama. Gue pengen data. Bukan aplikasi sensor yang udah jadi, bukan smart speaker. Gue pengen sesuatu yang gue bangun sendiri, nyimpen data dengan cara gue, dan ngasih tau gue tepat apa yang gue butuh.
Dari situlah lahir yang gue sebut smart home monitor. Sebuah daemon Python kecil yang polling smart plug berbasis Tuya setiap 60 detik, deteksi kapan pompa nyala atau mati, nulis data sesi ke MongoDB, dan kirim alert Telegram begitu ada perubahan state. Nggak perlu dashboard. Cukup notif di HP.
Artikel ini cerita lengkap proses bikin itu, deploy ke Fly.io, nemu masalah biaya, dan akhirnya pindahin ke server sendiri yang udah ada.
Apa yang Monitor Ini Kerjain
Sistemnya cuma punya satu tugas. Mantenin smart plug dan ngasih tau gue apa yang lagi terjadi sama pompa.
Setiap 60 detik, dia baca wattage dari Tuya Cloud API. Kalau wattage-nya lebih dari 100 watt, pompa dianggap nyala. Kalau di bawah itu, pompa mati.
Dari situ ada state machine dengan tiga transisi.
Waktu pompa baru nyala, sistem bikin dokumen sesi baru di MongoDB dan kirim alert Telegram warna hijau lengkap sama wattage dan voltase sekarang. Waktu pompa masih nyala, sistem nambah reading baru ke sesi yang sama. Waktu pompa mati, sistem finalisasi sesi dengan kalkulasi energi dan estimasi biaya dalam Rupiah, terus kirim alert Telegram warna merah.
Semua state disimpen di Redis di bawah satu key. Response dari Tuya API juga di-cache di Redis selama 50 detik biar nggak spam API tiap siklus.
Gambaran Arsitektur
Proyeknya cuma dua file Python aja, nih.
main.py start HTTP health server di port 8080 dan langsung jalanin fungsi polling pas startup. Terus serahin kendali ke APScheduler yang nembak fungsi itu setiap 60 detik pake timezone Asia/Jakarta.
monitor.py isinya semua business logic. Dia manage tiga singleton connection, satu ke Redis, satu ke Tuya, satu ke MongoDB. Kalau koneksi gagal, dia reset ke None dan reconnect di siklus berikutnya. Pattern ini yang bikin daemon tetep hidup tanpa perlu restart manual.
Ini versi disederhanakan dari fungsi polling utamanya.
def check_pompa_satelit() -> None:
r = _get_redis()
state = _load_state(r)
status = _fetch_device_status()
if status is None:
return
watt = status["watt"]
is_on = watt > ON_THRESHOLD
now = _now_ms()
if is_on and not state["isOn"]:
_save_state(r, {"isOn": True, "sessionId": "pending", ...})
_bg(_on_pump_on, now, watt, ...)
elif is_on and state["isOn"]:
_bg(_on_pump_still_on, state.get("sessionId"), now, watt)
elif not is_on and state["isOn"]:
_save_state(r, {"isOn": False, "sessionId": None, ...})
_bg(_on_pump_off, state, readings, now)Background handler-nya jalan di daemon thread biar nggak nge-block scheduler. MongoDB write dan Telegram message jalan di background sementara main loop terus polling sesuai jadwal.
Kalkulasi Energi
Waktu sesi selesai, sistem hitung total energi pake trapezoidal integration dari array readings. Ini lebih akurat dibanding sekadar kali daya rata-rata sama durasi.
total_wh = sum(
((readings[i]["watt"] + readings[i-1]["watt"]) / 2)
* ((readings[i]["timestamp"] - readings[i-1]["timestamp"]) / 3_600_000)
for i in range(1, len(readings))
)
total_kwh = total_wh / 1000
cost_rupiah = total_kwh * PLN_TARIFFKonstanta tarif di-set berdasarkan tarif listrik PLN yang berlaku. Hasilnya masuk ke alert Telegram terakhir lengkap sama durasi sesi, rata-rata watt, dan total energi yang dipake.
Review Kode Sebelum Deploy
Sebelum nyentuh infrastruktur apapun, gue jalanin syntax check dulu di kedua file.
python3 -c "import ast; ast.parse(open('main.py').read()); print('main.py OK')"
python3 -c "import ast; ast.parse(open('monitor.py').read()); print('monitor.py OK')"Keduanya lolos bersih. Gue juga review logic-nya buat edge case. State machine udah bener handle kasus kalau device status nggak tersedia. Dia skip siklus itu tanpa ubah state, jadi gagal API sementara nggak bakal corrupt data sesi.
Baca Juga: Setup OpenClaw di Server Remote: SSH Tunnel dan PM2
Deploy ke Fly.io
Gue paketin aplikasinya di Docker. Dockerfile-nya pake official Python slim image, install dependencies di layer terpisah buat caching, dan jalanin app sebagai non-root user.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -r -u 1000 app && chown -R app:app /app
USER app
EXPOSE 8080
CMD ["python", "main.py"]File fly.toml-nya set primary region paling deket sama Tuya API endpoint dan konfigurasi machine sebagai persistent worker yang nggak pernah auto-stop.
app = "nama-app-lo"
primary_region = "sjc"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = "off"
min_machines_running = 1
max_machines_running = 1
[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 256Gue bikin app-nya via Fly.io MCP server, set semua environment variable sebagai secrets, terus deploy pake CLI. Build-nya sekitar semenit dan log langsung nunjukin polling berhasil dalam beberapa detik setelah launch.
Jebakan Dua Machine
Fly.io secara default bikin dua machine buat high availability. Kedengerannya bagus tapi salah besar buat use case ini. Kalau dua machine jalan bareng, keduanya bakal polling device yang sama di waktu yang sama. Keduanya baca Redis state yang sama. Keduanya deteksi transisi yang sama dan kirim alert Telegram duplikat plus tulis MongoDB duplikat.
Gue langsung hancurin machine kedua dan tambahin max_machines_running = 1 di fly.toml biar nggak balik lagi pas deploy berikutnya.
Konfirmasi Jalan
Log-nya ngasih gambaran lengkap.
Health server on :8080
Initial check on startup
Checking Pompa Satelit
watt=0.0 volt=201.4 is_on=False (prev=False)
no_change
Scheduler started, polling every 60 sTerus tiap 60 detik setelah itu, pola yang sama berulang. Tuya API ngerespon. Redis state load dengan benar. Scheduler nembak tepat waktu.
Masalah Biaya
Beberapa menit setelah deploy, gue cek halaman pricing Fly.io. Ternyata free tier buat akun baru udah nggak ada. Shared CPU machine dengan 256 MB RAM yang jalan 24 jam sehari kena biaya sekitar dua dolar per bulan.
Dua dolar emang nggak banyak. Tapi gue udah punya server pribadi yang jalan sepanjang hari di rumah dan hampir nggak ngapa-ngapain. Mindahin monitor ke sana nggak kena biaya apapun dan ngapus satu baris pengeluaran bulanan yang nggak perlu.
Pindah ke Self-Hosted Server
Gue koneksi ke server lewat SSH dan bikin project directory di path yang lebih tersembunyi, jauh dari listing home directory default.
mkdir -p /home/nama-user/direktori-loGue copy file source pake scp dari mesin lokal.
scp main.py monitor.py requirements.txt .env \
nama-user@server-lo:/home/nama-user/direktori-lo/Server-nya belum ada package Python venv, jadi install dulu.
sudo apt install -y python3.12-venvTerus bikin virtual environment dan install semua dependency.
cd /home/nama-user/direktori-lo
python3 -m venv .venv
.venv/bin/pip install -r requirements.txtBaca Juga: Kubernetes Logging yang Bener: Fluent Bit ke Elasticsearch
Biar Tetep Jalan Pake Supervisor
Server gue jalan di dalam container environment tanpa systemd. Gue install Supervisor sebagai process manager. Dia handle auto-restart dan log rotation tanpa butuh full init system.
sudo apt install -y supervisorGue bikin config file Supervisor buat monitor-nya.
[program:smarthome-monitor]
command=/home/nama-user/direktori-lo/.venv/bin/python main.py
directory=/home/nama-user/direktori-lo
user=nama-username-lo
autostart=true
autorestart=true
startretries=10
stderr_logfile=/var/log/smarthome-monitor.err.log
stdout_logfile=/var/log/smarthome-monitor.out.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3Setelah reload Supervisor dan start program-nya, statusnya langsung RUNNING.
smarthome-monitor RUNNING pid 1234, uptime 0:01:00Error log-nya konfirmasi semuanya sehat.
Health server on :8080
Initial check on startup
Checking Pompa Satelit
watt=0.0 volt=199.8 is_on=False (prev=False)
no_change
Scheduler started, polling every 60 s
Running job check_pompa_satelit at 2026-03-01 16:05:58 WIB
Checking Pompa Satelit
watt=0.0 volt=199.8 is_on=False (prev=False)
no_change
Job check_pompa_satelit executed successfullyCommand Sehari-hari
Ini command yang bakal lo pake buat manage monitor di server.
# Cek status
sudo supervisorctl status
# Lihat log live
sudo tail -f /var/log/smarthome-monitor.err.log
# Restart process
sudo supervisorctl restart smarthome-monitor
# Stop process
sudo supervisorctl stop smarthome-monitorYang Gue Pelajari dari Build Ini
Proyek ini ngasih beberapa pelajaran yang worth dituliskan.
Polling daemon harus jalan sebagai satu instance aja, deh. High availability itu jebakan buat stateful worker. Kalau state lo ada di shared cache kayak Redis, dua worker yang race ke key yang sama bakal bikin duplikat dan corrupt data sesi. Satu machine, selalu.
Credentials harus selalu di environment variable. Entah lo pake Fly.io secrets, file .env, atau secret manager, jangan hardcode credentials. Kode di proyek ini nggak pernah nyentuh credential langsung. Semua datang dari environment.
Supervisor itu underrated banget, nih. Dia kerja bagus di container environment tanpa systemd. Tiga hal yang dia kerjain dengan baik: start process, restart waktu crash, dan tulis log. Itu semua yang lo butuh buat background worker.
Jalanin di server sendiri hemat uang dan kasih lo kontrol penuh. Migrasi dari Fly.io ke self-hosted butuh kurang dari sejam dan nggak ada biaya tambahan. Kalau lo udah punya hardware, pake aja, yuk.
Penutup
Monitor ini mulai dari rasa penasaran soal tagihan listrik. Jadinya sistem yang beneran kerja dan kasih gue data nyata tiap hari. History sesi pompa di MongoDB udah nunjukin pola yang sebelumnya nggak keliatan. Jam pemakaian puncak, durasi yang lebih panjang dari ekspektasi, dan estimasi biaya yang hampir persis sama sama tagihan asli gue.
Kode-nya kecil dan straightforward. Dua file, enam dependency, tanpa framework. Jalan diam-diam di background tanpa maintenance apapun. Waktu pompa nyala, gue dapat notif. Waktu mati, gue dapat estimasi biaya. Itu persis yang gue mau dari awal.
Kalau lo punya smart plug Tuya dan server nganggur, stack ini worth banget buat dibangun sendiri.


