1. Apa Itu OOP?
Object-Oriented Programming (OOP) adalah paradigma pemrograman yang mengorganisir kode ke dalam bentuk "objek" — entitas yang memiliki data (atribut/properti) dan perilaku (method/fungsi). Berbeda dari pemrograman prosedural yang berfokus pada urutan eksekusi fungsi, OOP berfokus pada modeling dunia nyata ke dalam kode.
Dalam konteks Internet of Things (IoT), OOP menjadi sangat powerful karena sistem IoT secara alami terdiri dari entitas-entitas yang bisa dimodelkan sebagai objek: sensor, aktuator, perangkat, gateway, pesan, dan lainnya. Setiap entitas ini memiliki data tersendiri dan perilaku tersendiri — persis seperti konsep objek dalam OOP.
Empat Pilar OOP
| Pilar | Penjelasan Singkat | Contoh di IoT |
|---|---|---|
| Inheritance | Kelas anak mewarisi atribut & method dari kelas induk | SensorSuhu mewarisi dari kelas Sensor |
| Polymorphism | Method yang sama bisa berperilaku berbeda di tiap kelas | Metode baca() berbeda untuk sensor suhu vs sensor gas |
| Encapsulation | Menyembunyikan detail internal, hanya ekspos interface publik | Method kalibrasi sensor disembunyikan, hanya get_data() yang publik |
| Abstraction | Menyederhanakan kompleksitas dengan abstraksi tingkat tinggi | Kelas abstrak Device tanpa detail spesifik hardware |
OOP vs Pemrograman Prosedural
PROSEDURAL OOP
────────── ───
def baca_suhu(): class Sensor:
def baca_kelembaban(): def baca(self):
def kirim_mqtt(): def kirim(self):
def cek_threshold(): def cek_threshold(self):
data = baca_suhu() sensor = SensorSuhu(pin=4)
if data > 30: data = sensor.baca()
kirim_mqtt("panas") if data > 30:
sensor.kirim("panas")
Fokus: urutan fungsi Fokus: objek & interaksi
Python secara teknis mendukung multi-paradigma — Anda bisa menulis kode prosedural, OOP, atau bahkan fungsional dalam satu file. Namun untuk proyek IoT yang berkembang, OOP akan menghemat banyak waktu karena kode lebih terstruktur dan mudah di-maintain.
2. Classes & Objects
Class adalah blueprint (cetakan) untuk membuat objek. Ibarat arsitektur rumah — satu desain bisa menghasilkan banyak rumah. Sedangkan object adalah instance (kejadian) nyata dari class tersebut — rumah yang sudah dibangun dari desain tadi.
2.1 — Membuat Class Pertama
"""
Contoh Class Sensor Sederhana
BeebaneLabs - https://beebanelabs.pages.dev
"""
import random
import time
class Sensor:
"""Class dasar untuk semua sensor IoT."""
# Class variable — dimiliki oleh SEMUA instance
jumlah_sensor = 0
def __init__(self, nama, pin, satuan=""):
"""
Constructor — dipanggil otomatis saat object dibuat.
Parameters:
nama : Nama identifikasi sensor
pin : Pin GPIO yang digunakan
satuan : Satuan pengukuran (°C, %, ppm, dll)
"""
# Instance variable — dimiliki oleh masing-masing object
self.nama = nama
self.pin = pin
self.satuan = satuan
self.data_terakhir = None
self.aktif = False
# Update class variable
Sensor.jumlah_sensor += 1
self.id = Sensor.jumlah_sensor
def aktifkan(self):
"""Mengaktifkan sensor."""
self.aktif = True
print(f"[{self.nama}] Sensor diaktifkan pada pin {self.pin}")
def matikan(self):
"""Menonaktifkan sensor."""
self.aktif = False
print(f"[{self.nama}] Sensor dimatikan")
def baca(self):
"""Membaca data dari sensor (simulasi)."""
if not self.aktif:
print(f"[{self.nama}] ERROR: Sensor belum aktif!")
return None
# Simulasi pembacaan
self.data_terakhir = round(random.uniform(20.0, 40.0), 2)
return self.data_terakhir
def status(self):
"""Mengembalikan status sensor."""
return {
"id": self.id,
"nama": self.nama,
"pin": self.pin,
"aktif": self.aktif,
"data_terakhir": self.data_terakhir
}
def __str__(self):
"""Representasi string dari object."""
status_text = "AKTIF" if self.aktif else "NONAKTIF"
return f"Sensor('{self.nama}', pin={self.pin}, status={status_text})"
# --- Membuat Object (Instance) dari Class ---
sensor_suhu = Sensor("Sensor Suhu DHT22", pin=4, satuan="°C")
sensor_gas = Sensor("Sensor Gas MQ-135", pin=17, satuan="ppm")
print(sensor_suhu) # Sensor('Sensor Suhu DHT22', pin=4, status=NONAKTIF)
print(f"Total sensor: {Sensor.jumlah_sensor}") # Total sensor: 2
# Mengaktifkan dan membaca
sensor_suhu.aktifkan()
data = sensor_suhu.baca()
print(f"Suhu: {data}{sensor_suhu.satuan}") # Suhu: 28.45°C
print(sensor_suhu.status())
2.2 — Memahami __init__ dan self
| Konsep | Penjelasan |
|---|---|
__init__ | Constructor — method khusus yang otomatis dipanggil saat object dibuat. Digunakan untuk inisialisasi atribut awal. |
self | Referensi ke object itu sendiri. Setiap method dalam class harus menerima self sebagai parameter pertama agar bisa mengakses atribut object. |
__str__ | Method khusus yang dipanggil saat kita menggunakan print(object) atau str(object). |
| Class variable | Variabel yang dimiliki oleh class itu sendiri (bukan instance). Diakses via NamaClass.var. |
| Instance variable | Variabel yang dimiliki oleh masing-masing object. Diakses via self.var. |
3. Inheritance (Pewarisan)
Inheritance memungkinkan kita membuat class baru berdasarkan class yang sudah ada. Class baru (anak/subclass) mewarisi semua atribut dan method dari class induk (parent/superclass), dan bisa menambahkan atau meng-override fitur sesuai kebutuhan.
Dalam proyek IoT, inheritance sangat berguna karena berbagai jenis sensor memiliki karakteristik dasar yang sama (nama, pin, status aktif) tetapi cara pembacaan datanya berbeda-beda.
"""
Inheritance pada Class Sensor IoT
BeebaneLabs - https://beebanelabs.pages.dev
"""
import random
import time
class Sensor:
"""Class dasar (parent) untuk semua sensor."""
def __init__(self, nama, pin, satuan=""):
self.nama = nama
self.pin = pin
self.satuan = satuan
self.data_terakhir = None
self.aktif = False
self.riwayat = [] # Menyimpan history pembacaan
def aktifkan(self):
self.aktif = True
print(f"[{self.nama}] Aktif pada pin {self.pin}")
def matikan(self):
self.aktif = False
print(f"[{self.nama}] Dimatikan")
def baca(self):
"""Method dasar — akan di-override oleh subclass."""
raise NotImplementedError("Subclass harus mengimplementasikan method baca()")
def simpan_riwayat(self, nilai):
"""Menyimpan data ke riwayat pembacaan."""
entry = {
"waktu": time.strftime("%Y-%m-%d %H:%M:%S"),
"nilai": nilai
}
self.riwayat.append(entry)
# Batasi riwayat maksimal 100 entri
if len(self.riwayat) > 100:
self.riwayat.pop(0)
def rata_rata(self):
"""Menghitung rata-rata dari riwayat pembacaan."""
if not self.riwayat:
return None
nilai_list = [e["nilai"] for e in self.riwayat]
return round(sum(nilai_list) / len(nilai_list), 2)
# ---- Child Class: Sensor Suhu ----
class SensorSuhu(Sensor):
"""Sensor suhu DHT11/DHT22."""
def __init__(self, nama, pin, tipe_sensor="DHT22"):
super().__init__(nama, pin, satuan="°C")
self.tipe_sensor = tipe_sensor
self.threshold_panas = 35.0
def baca(self):
"""Override method baca() untuk sensor suhu."""
if not self.aktif:
return None
# Simulasi: suhu antara 22-38°C
suhu = round(random.uniform(22.0, 38.0), 1)
self.data_terakhir = suhu
self.simpan_riwayat(suhu)
return suhu
def cek_panas(self):
"""Method khusus sensor suhu."""
if self.data_terakhir and self.data_terakhir > self.threshold_panas:
return True
return False
# ---- Child Class: Sensor Kelembaban ----
class SensorKelembaban(Sensor):
"""Sensor kelembaban tanah atau udara."""
def __init__(self, nama, pin, tipe="tanah"):
super().__init__(nama, pin, satuan="%")
self.tipe = tipe # "tanah" atau "udara"
self.threshold_kering = 30.0
def baca(self):
"""Override method baca() untuk sensor kelembaban."""
if not self.aktif:
return None
kelembaban = round(random.uniform(20.0, 95.0), 1)
self.data_terakhir = kelembaban
self.simpan_riwayat(kelembaban)
return kelembaban
def cek_kering(self):
"""Method khusus sensor kelembaban."""
if self.data_terakhir and self.data_terakhir < self.threshold_kering:
return True
return False
# ---- Child Class: Sensor Gas ----
class SensorGas(Sensor):
"""Sensor gas MQ-135 / MQ-2."""
def __init__(self, nama, pin, tipe_gas="CO2"):
super().__init__(nama, pin, satuan="ppm")
self.tipe_gas = tipe_gas
self.threshold_bahaya = 1000
def baca(self):
"""Override method baca() untuk sensor gas."""
if not self.aktif:
return None
ppm = round(random.uniform(200, 2000), 0)
self.data_terakhir = ppm
self.simpan_riwayat(ppm)
return ppm
def cek_bahaya(self):
"""Method khusus sensor gas."""
if self.data_terakhir and self.data_terakhir > self.threshold_bahaya:
return True
return False
# --- Penggunaan ---
suhu = SensorSuhu("DHT22 Ruang Tamu", pin=4)
kelembaban = SensorKelembaban("Soil Moisture", pin=17, tipe="tanah")
gas = SensorGas("MQ-135 Dapur", pin=27, tipe_gas="LPG")
# Aktifkan semua
for sensor in [suhu, kelembaban, gas]:
sensor.aktifkan()
# Baca data beberapa kali
for i in range(5):
s = suhu.baca()
k = kelembaban.baca()
g = gas.baca()
print(f"Pembacaan {i+1}: Suhu={s}°C, Kelembaban={k}%, Gas={g}ppm")
time.sleep(0.5)
# Cek rata-rata
print(f"\nRata-rata suhu: {suhu.rata_rata()}°C")
print(f"Suhu panas? {suhu.cek_panas()}")
print(f"Tanah kering? {kelembaban.cek_kering()}")
print(f"Gas bahaya? {gas.cek_bahaya()}")
# isinstance() untuk mengecek tipe
print(f"\nApakah suhu termasuk Sensor? {isinstance(suhu, Sensor)}") # True
print(f"Apakah gas termasuk SensorSuhu? {isinstance(gas, SensorSuhu)}") # False
super() dan Method Resolution Order
Fungsi super() memanggil method dari class induk. Ini penting agar kita tidak perlu menulis ulang kode yang sudah ada di parent. Saat memanggil super().__init__(), kita meneruskan parameter ke constructor parent sehingga atribut dasar seperti nama, pin, dan satuan tetap terinisialisasi.
4. Polymorphism (Bentuk Jamak)
Polymorphism berarti "banyak bentuk" — memungkinkan method yang sama memiliki implementasi berbeda di setiap class. Dalam Python, polymorphism bekerja secara natural karena Python menggunakan duck typing: jika sebuah object memiliki method yang dibutuhkan, ia bisa digunakan tanpa peduli class-nya.
"""
Polymorphism dalam Sistem IoT
BeebaneLabs - https://beebanelabs.pages.dev
"""
import random
import json
class Aktuator:
"""Class dasar aktuator."""
def __init__(self, nama, pin):
self.nama = nama
self.pin = pin
self.status = False
def nyalakan(self):
self.status = True
print(f"[{self.nama}] DINYALAKAN")
def matikan(self):
self.status = False
print(f"[{self.nama}] DIMATIKAN")
def eksekusi(self, perintah):
"""Method polymorphic — di-override oleh subclass."""
raise NotImplementedError
class Lampu(Aktuator):
"""Aktuator lampu."""
def __init__(self, nama, pin, warna="putih"):
super().__init__(nama, pin)
self.warna = warna
self.brightness = 100
def eksekusi(self, perintah):
if perintah == "nyalakan":
self.nyalakan()
elif perintah == "matikan":
self.matikan()
elif perintah.startswith("brightness:"):
level = int(perintah.split(":")[1])
self.brightness = max(0, min(100, level))
print(f"[{self.nama}] Brightness: {self.brightness}%")
return self.status
class Relay(Aktuator):
"""Aktuator relay (untuk perangkat high-power)."""
def __init__(self, nama, pin, tipe="NO"):
super().__init__(nama, pin)
self.tipe = tipe # NO (Normally Open) atau NC (Normally Closed)
def eksekusi(self, perintah):
if perintah == "nyalakan":
self.nyalakan()
elif perintah == "matikan":
self.matikan()
elif perintah == "toggle":
if self.status:
self.matikan()
else:
self.nyalakan()
return self.status
class Buzzer(Aktuator):
"""Aktuator buzzer/speaker."""
def __init__(self, nama, pin):
super().__init__(nama, pin)
self.pattern = "continuous"
def eksekusi(self, perintah):
if perintah == "alarm":
self.nyalakan()
self.pattern = "alarm"
print(f"[{self.nama}] 🔊 BUNYI ALARM!")
elif perintah == "beep":
print(f"[{self.nama}] Bip!")
elif perintah == "matikan":
self.matikan()
self.pattern = "continuous"
return self.status
# --- Polymorphism: satu fungsi, berbagai perilaku ---
def kontrol_semua(aktuator_list, perintah):
"""Mengirim perintah ke semua aktuator — polymorphism!"""
for aktuator in aktuator_list:
aktuator.eksekusi(perintah)
# Buat berbagai aktuator
lampu_ruang = Lampu("Lampu Ruang Tamu", pin=18, warna="kuning")
relay_pompa = Relay("Relay Pompa Air", pin=23)
buzzer_alarm = Buzzer("Buzzer Alarm", pin=24)
semua_aktuator = [lampu_ruang, relay_pompa, buzzer_alarm]
# Polymorphism: semua object punya .eksekusi() tapi berperilaku beda
print("=== Semua Aktuator ON ===")
kontrol_semua(semua_aktuator, "nyalakan")
print("\n=== Semua Aktuator OFF ===")
kontrol_semua(semua_aktuator, "matikan")
# Duck typing — tidak peduli tipe, asal punya method yang sama
print("\n=== Duck Typing ===")
for aktuator in semua_aktuator:
# Semua punya .eksekusi(), jadi bisa dipanggil seragam
aktuator.eksekusi("nyalakan")
print(f" → {aktuator.nama}: status={aktuator.status}")
aktuator.eksekusi("matikan")
Duck typing berarti Python tidak memeriksa tipe object, hanya memeriksa apakah object tersebut memiliki method yang dibutuhkan. "Jika berjalan seperti bebek dan berkwek seperti bebek, maka itu bebek." Ini membuat kode Python sangat fleksibel untuk sistem IoT yang punya banyak jenis perangkat.
5. Encapsulation (Pembungkusan)
Encapsulation adalah prinsip menyembunyikan detail internal object dan hanya mengekspos interface yang aman. Dalam Python, ini dilakukan dengan konvensi penamaan: single underscore _ untuk "protected" dan double underscore __ untuk "private" (name mangling).
"""
Encapsulation pada Device IoT
BeebaneLabs - https://beebanelabs.pages.dev
"""
class DeviceSecure:
"""
Contoh encapsulation pada perangkat IoT.
Atribut private dilindungi dari akses langsung.
"""
def __init__(self, nama, ip_address, api_key):
self.nama = nama # Public
self._ip_address = ip_address # Protected (konvensi)
self.__api_key = api_key # Private (name mangling)
self.__koneksi_aktif = False
self.__log = []
# --- Property: getter ---
@property
def status_koneksi(self):
"""Mengecek status koneksi tanpa mengakses atribut private."""
return "TERHUBUNG" if self.__koneksi_aktif else "TERPUTUS"
@property
def ip(self):
"""Getter untuk IP (read-only)."""
return self._ip_address
# --- Method publik (interface) ---
def hubungkan(self):
"""Menghubungkan device — interface publik."""
if self.__validasi_api_key():
self.__koneksi_aktif = True
self.__catat_log("Koneksi berhasil")
print(f"[{self.nama}] Terhubung ke {self._ip_address}")
else:
self.__catat_log("Koneksi GAGAL: API key invalid")
print(f"[{self.nama}] GAGAL: API key tidak valid")
def putuskan(self):
"""Memutuskan koneksi."""
self.__koneksi_aktif = False
self.__catat_log("Koneksi diputus")
print(f"[{self.nama}] Koneksi diputus")
def kirim_data(self, data):
"""Mengirim data — hanya jika terhubung."""
if not self.__koneksi_aktif:
print(f"[{self.nama}] ERROR: Tidak terhubung!")
return False
self.__catat_log(f"Data dikirim: {data}")
print(f"[{self.nama}] Data terkirim: {data}")
return True
def dapatkan_log(self):
"""Mengembalikan salinan log (bukan referensi asli)."""
return self.__log.copy()
# --- Method private ---
def __validasi_api_key(self):
"""Validasi internal — tidak bisa diakses dari luar."""
return len(self.__api_key) >= 10
def __catat_log(self, pesan):
"""Logging internal."""
import time
entry = f"[{time.strftime('%H:%M:%S')}] {pesan}"
self.__log.append(entry)
# --- Penggunaan ---
device = DeviceSecure("Gateway Utama", "192.168.1.100", "rahasia-api-key-12345")
# Akses publik — aman
print(device.nama) # Gateway Utama
print(device.status_koneksi) # TERPUTUS
print(device.ip) # 192.168.1.100
device.hubungkan()
print(device.status_koneksi) # TERHUBUNG
device.kirim_data({"suhu": 28.5})
# Akses private — akan error atau name mangling
try:
print(device.__api_key) # AttributeError!
except AttributeError as e:
print(f"Error: {e}") # 'DeviceSecure' object has no attribute '__api_key'
# Log hanya bisa diakses via method publik
print(f"Log entries: {len(device.dapatkan_log())}")
Python tidak memiliki enkripsi "sejati" untuk atribut private. Double underscore __ hanya melakukan name mangling (atribut menjadi _NamaClass__atribut). Namun, ini sudah cukup untuk mencegah akses tidak sengaja dan mengirimkan pesan jelas bahwa atribut tersebut tidak boleh diakses langsung.
6. Abstraction (Abstraksi)
Abstraction adalah prinsip menyederhanakan kompleksitas dengan hanya menampilkan fitur yang relevan kepada pengguna. Dalam Python, abstraksi diimplementasikan menggunakan modul abc (Abstract Base Classes) yang memungkinkan kita mendefinisikan class abstrak — class yang tidak bisa diinstansiasi langsung dan harus di-override oleh subclass.
"""
Abstraction untuk Sistem IoT
BeebaneLabs - https://beebanelabs.pages.dev
"""
from abc import ABC, abstractmethod
import json
import time
import random
class PerangkatIoT(ABC):
"""
Abstract Base Class untuk semua perangkat IoT.
Tidak bisa diinstansiasi langsung — HARUS di-override.
"""
def __init__(self, nama, lokasi):
self.nama = nama
self.lokasi = lokasi
self._terhubung = False
@abstractmethod
def inisialisasi(self):
"""Inisialisasi hardware — WAJIB diimplementasikan."""
pass
@abstractmethod
def baca_data(self):
"""Membaca data dari perangkat — WAJIB diimplementasikan."""
pass
@abstractmethod
def kirim_ke_server(self, data):
"""Mengirim data ke server — WAJIB diimplementasikan."""
pass
# Concrete method — sudah diimplementasikan, bisa dipakai langsung
def format_json(self, data):
"""Format data menjadi JSON string."""
payload = {
"device": self.nama,
"lokasi": self.lokasi,
"waktu": time.strftime("%Y-%m-%d %H:%M:%S"),
"data": data
}
return json.dumps(payload)
def status(self):
"""Mengembalikan status perangkat."""
return f"{self.nama} @ {self.lokasi} — {'ONLINE' if self._terhubung else 'OFFLINE'}"
class StasiunCuaca(PerangkatIoT):
"""Implementasi konkret: Stasiun pemantau cuaca."""
def __init__(self, nama, lokasi):
super().__init__(nama, lokasi)
self.sensor_suhu = None
self.sensor_kelembaban = None
self.sensor_tekanan = None
def inisialisasi(self):
"""Implementasi inisialisasi untuk stasiun cuaca."""
self.sensor_suhu = True
self.sensor_kelembaban = True
self.sensor_tekanan = True
self._terhubung = True
print(f"[{self.nama}] Stasiun cuaca siap — 3 sensor aktif")
def baca_data(self):
"""Implementasi pembacaan data stasiun cuaca."""
if not self._terhubung:
return None
return {
"suhu": round(random.uniform(22, 35), 1),
"kelembaban": round(random.uniform(40, 90), 1),
"tekanan": round(random.uniform(1000, 1025), 1)
}
def kirim_ke_server(self, data):
"""Implementasi pengiriman data ke server."""
json_data = self.format_json(data)
print(f"[{self.nama}] Mengirim: {json_data}")
return True
class PemantauAir(PerangkatIoT):
"""Implementasi konkret: Pemantau kualitas air."""
def __init__(self, nama, lokasi):
super().__init__(nama, lokasi)
self.sensor_ph = None
self.sensor_turbidity = None
def inisialisasi(self):
self.sensor_ph = True
self.sensor_turbidity = True
self._terhubung = True
print(f"[{self.nama}] Pemantau air siap — 2 sensor aktif")
def baca_data(self):
if not self._terhubung:
return None
return {
"ph": round(random.uniform(6.5, 8.5), 2),
"turbidity": round(random.uniform(0, 100), 1),
"suhu_air": round(random.uniform(20, 30), 1)
}
def kirim_ke_server(self, data):
json_data = self.format_json(data)
print(f"[{self.nama}] Mengirim: {json_data}")
return True
# --- Penggunaan ---
# PerangkatIoT("test", "sini") # TypeError: Can't instantiate abstract class!
# Buat instance dari subclass
cuaca = StasiunCuaca("Stasiun A", "Atap Gedung")
air = PemantauAir("Pemantau Kolam", "Kolam Ikan")
# Inisialisasi
cuaca.inisialisasi()
air.inisialisasi()
# Baca dan kirim data
data_cuaca = cuaca.baca_data()
cuaca.kirim_ke_server(data_cuaca)
data_air = air.baca_data()
air.kirim_ke_server(data_air)
# Polymorphism: perlakukan semua perangkat sama
print("\n=== Status Semua Perangkat ===")
for perangkat in [cuaca, air]:
print(perangkat.status())
7. OOP dalam Konteks IoT
Sekarang mari kita terapkan semua konsep OOP ke dalam skenario nyata IoT. Kita akan membangun sistem yang terdiri dari kelas Sensor dan Device yang saling berinteraksi.
7.1 — Class Sensor untuk Berbagai Tipe
"""
Class Sensor untuk Sistem IoT
BeebaneLabs - https://beebanelabs.pages.dev
"""
from abc import ABC, abstractmethod
import random
import time
class SensorBase(ABC):
"""Abstract base class untuk semua sensor."""
def __init__(self, nama, pin, satuan="", interval_baca=5):
self.nama = nama
self.pin = pin
self.satuan = satuan
self.interval_baca = interval_baca
self._aktif = False
self._data_buffer = []
self._max_buffer = 50
@abstractmethod
def _baca_hardware(self):
"""Akses hardware sesungguhnya — override di subclass."""
pass
def aktifkan(self):
self._aktif = True
print(f"[{self.nama}] ✓ Sensor aktif (pin {self.pin})")
def baca(self):
if not self._aktif:
return None
try:
nilai = self._baca_hardware()
self._buffer_data(nilai)
return nilai
except Exception as e:
print(f"[{self.nama}] Error baca: {e}")
return None
def _buffer_data(self, nilai):
entry = {"waktu": time.time(), "nilai": nilai}
self._data_buffer.append(entry)
if len(self._data_buffer) > self._max_buffer:
self._data_buffer.pop(0)
def statistik(self):
if not self._data_buffer:
return None
nilai_list = [d["nilai"] for d in self._data_buffer]
return {
"min": min(nilai_list),
"max": max(nilai_list),
"avg": round(sum(nilai_list) / len(nilai_list), 2),
"count": len(nilai_list)
}
class SensorDHT(SensorBase):
"""Sensor suhu dan kelembaban DHT11/DHT22."""
def __init__(self, nama, pin, tipe="DHT22"):
super().__init__(nama, pin, satuan="°C/%", interval_baca=3)
self.tipe = tipe
self.suhu = None
self.kelembaban = None
def _baca_hardware(self):
self.suhu = round(random.uniform(22, 38), 1)
self.kelembaban = round(random.uniform(30, 90), 1)
return {"suhu": self.suhu, "kelembaban": self.kelembaban}
def baca(self):
data = super().baca()
return data
class SensorUltrasonik(SensorBase):
"""Sensor jarak ultrasonik HC-SR04."""
def __init__(self, nama, pin_trigger, pin_echo):
super().__init__(nama, pin_trigger, satuan="cm", interval_baca=1)
self.pin_echo = pin_echo
def _baca_hardware(self):
jarak = round(random.uniform(2, 400), 1)
return jarak
class SensorLDR(SensorBase):
"""Sensor cahaya LDR."""
def __init__(self, nama, pin):
super().__init__(nama, pin, satuan="lux", interval_baca=5)
def _baca_hardware(self):
cahaya = round(random.uniform(0, 1023), 0)
return cahaya
# --- Contoh Penggunaan ---
dht = SensorDHT("DHT22 Ruang Server", pin=4)
jarak = SensorUltrasonik("HC-SR04 Tangki", pin_trigger=17, pin_echo=27)
cahaya = SensorLDR("LDR Taman", pin=22)
# Aktifkan semua
for s in [dht, jarak, cahaya]:
s.aktifkan()
# Baca beberapa kali
for i in range(10):
print(f"DHT: {dht.baca()}, Jarak: {jarak.baca()}cm, Cahaya: {cahaya.baca()}lux")
time.sleep(0.2)
# Statistik
print(f"\nStatistik DHT: {dht.statistik()}")
print(f"Statistik Jarak: {jarak.statistik()}")
7.2 — Class Device sebagai Controller
"""
Class Device sebagai IoT Controller
BeebaneLabs - https://beebanelabs.pages.dev
"""
import time
import json
from datetime import datetime
class IoTDevice:
"""
Device controller yang mengelola sensor dan aktuator.
Menjadi 'otak' dari node IoT.
"""
def __init__(self, nama, device_id, lokasi=""):
self.nama = nama
self.device_id = device_id
self.lokasi = lokasi
self._sensor_list = {}
self._aktuator_list = {}
self._rules = []
self._running = False
def tambah_sensor(self, key, sensor):
"""Menambahkan sensor ke device."""
self._sensor_list[key] = sensor
print(f"[{self.nama}] Sensor '{key}' ditambahkan ({sensor.nama})")
def tambah_aktuator(self, key, aktuator):
"""Menambahkan aktuator ke device."""
self._aktuator_list[key] = aktuator
print(f"[{self.nama}] Aktuator '{key}' ditambahkan ({aktuator.nama})")
def tambah_rule(self, kondisi_fn, aksi_fn, deskripsi=""):
"""Menambahkan aturan otomatis."""
self._rules.append({
"kondisi": kondisi_fn,
"aksi": aksi_fn,
"deskripsi": deskripsi
})
print(f"[{self.nama}] Rule ditambahkan: {deskripsi}")
def baca_semua_sensor(self):
"""Membaca semua sensor dan mengembalikan dict data."""
hasil = {}
for key, sensor in self._sensor_list.items():
hasil[key] = sensor.baca()
return hasil
def jalankan_rules(self, data):
"""Evaluasi dan jalankan semua rules."""
for rule in self._rules:
try:
if rule["kondisi"](data):
rule["aksi"](self._aktuator_list, data)
print(f"[{self.nama}] Rule dijalankan: {rule['deskripsi']}")
except Exception as e:
print(f"[{self.nama}] Rule error: {e}")
def siklus(self):
"""Satu siklus baca-evaluasi-aksi."""
print(f"\n--- Siklus {datetime.now().strftime('%H:%M:%S')} ---")
data = self.baca_semua_sensor()
print(f"Data sensor: {json.dumps(data, default=str)}")
self.jalankan_rules(data)
return data
def jalankan(self, interval=5, max_siklus=None):
"""Menjalankan device dalam loop."""
self._running = True
siklus = 0
print(f"\n[{self.nama}] === DEVICE STARTED ===")
# Aktifkan semua sensor
for sensor in self._sensor_list.values():
sensor.aktifkan()
try:
while self._running:
self.siklus()
siklus += 1
if max_siklus and siklus >= max_siklus:
break
time.sleep(interval)
except KeyboardInterrupt:
print(f"\n[{self.nama}] Dihentikan oleh user")
finally:
self._running = False
print(f"[{self.nama}] === DEVICE STOPPED ===")
def stop(self):
"""Menghentikan device."""
self._running = False
# --- Membuat Sistem IoT Lengkap ---
# (Asumsi class SensorDHT, Lampu, Relay dari contoh sebelumnya tersedia)
# device = IoTDevice("Smart Room", "SR-001", "Ruang Server")
# device.tambah_sensor("suhu", SensorDHT("DHT22", pin=4))
# device.tambah_aktuator("lampu", Lampu("LED Indikator", pin=18))
# device.tambah_aktuator("kipas", Relay("Kipas Exhaust", pin=23))
#
# # Tambah aturan otomatis
# device.tambah_rule(
# kondisi_fn=lambda data: data.get("suhu", {}).get("suhu", 0) > 30,
# aksi_fn=lambda akt, data: akt["kipas"].eksekusi("nyalakan"),
# deskripsi="Nyalakan kipas jika suhu > 30°C"
# )
# device.tambah_rule(
# kondisi_fn=lambda data: data.get("suhu", {}).get("suhu", 0) <= 30,
# aksi_fn=lambda akt, data: akt["kipas"].eksekusi("matikan"),
# deskripsi="Matikan kipas jika suhu <= 30°C"
# )
#
# # Jalankan 5 siklus
# device.jalankan(interval=2, max_siklus=5)
┌─────────────────────────────────────────────────┐ │ IoTDevice │ │ ┌─────────────┐ ┌──────────────────────┐ │ │ │ Sensors │ │ Rules Engine │ │ │ │ ┌─────────┐ │ │ ┌──────────────────┐ │ │ │ │ │ DHT22 │ │────►│ │ if suhu > 30: │ │ │ │ │ │ HC-SR04 │ │ │ │ nyalakan(kipas)│ │ │ │ │ │ LDR │ │ │ └──────────────────┘ │ │ │ │ └─────────┘ │ └──────────────────────┘ │ │ └─────────────┘ │ │ │ ▼ │ │ ┌─────────────┐ ┌──────────────────────┐ │ │ │ Data Store │ │ Aktuators │ │ │ │ ┌─────────┐ │ │ ┌──────────────────┐ │ │ │ │ │ Buffer │ │ │ │ Lampu, Relay, │ │ │ │ │ │ Stats │ │ │ │ Buzzer, Motor │ │ │ │ │ └─────────┘ │ │ └──────────────────┘ │ │ │ └─────────────┘ └──────────────────────┘ │ └─────────────────────────────────────────────────┘
8. Design Patterns untuk IoT
Design patterns adalah solusi template untuk masalah desain yang umum. Dalam pengembangan sistem IoT, beberapa pattern sangat membantu untuk membuat kode yang bersih dan scalable.
8.1 — Singleton Pattern
Memastikan hanya ada satu instance dari sebuah class — cocok untuk MQTT client, database connection, atau konfigurasi global.
"""
Singleton Pattern untuk MQTT Manager
BeebaneLabs - https://beebanelabs.pages.dev
"""
class MQTTManager:
"""Singleton — hanya satu instance yang boleh ada."""
_instance = None
_initialized = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, broker="localhost", port=1883):
if MQTTManager._initialized:
return
self.broker = broker
self.port = port
self._connected = False
self._subscribers = {}
MQTTManager._initialized = True
print(f"[MQTT] Manager dibuat — broker: {broker}:{port}")
def connect(self):
self._connected = True
print(f"[MQTT] Terhubung ke {self.broker}:{self.port}")
def subscribe(self, topic, callback):
self._subscribers[topic] = callback
print(f"[MQTT] Subscribe: {topic}")
# Semua variabel ini menunjuk ke INSTANCE YANG SAMA
mqtt1 = MQTTManager("192.168.1.10", 1883)
mqtt2 = MQTTManager("broker-lain.com", 8883) # Parameter diabaikan!
print(f"mqtt1 is mqtt2: {mqtt1 is mqtt2}") # True
print(f"Broker: {mqtt2.broker}") # 192.168.1.10 (tetap dari inisialisasi pertama)
8.2 — Observer Pattern
Membangun sistem event-driven di mana perubahan pada satu komponen (sensor) secara otomatis memberitahu komponen lain (aktuator, logger, notifikasi).
"""
Observer Pattern untuk Event-Driven IoT
BeebaneLabs - https://beebanelabs.pages.dev
"""
class EventEmitter:
"""Base class untuk event system."""
def __init__(self):
self._listeners = {}
def on(self, event_name, callback):
"""Mendaftarkan listener untuk event tertentu."""
if event_name not in self._listeners:
self._listeners[event_name] = []
self._listeners[event_name].append(callback)
def emit(self, event_name, data=None):
"""Memancarkan event ke semua listener."""
if event_name in self._listeners:
for callback in self._listeners[event_name]:
callback(data)
class SensorMonitor(EventEmitter):
"""Sensor yang memancarkan event saat threshold terlampaui."""
def __init__(self, nama, threshold_high=None, threshold_low=None):
super().__init__()
self.nama = nama
self.threshold_high = threshold_high
self.threshold_low = threshold_low
self._nilai_terakhir = None
def update(self, nilai):
self._nilai_terakhir = nilai
if self.threshold_high and nilai > self.threshold_high:
self.emit("threshold_high", {
"sensor": self.nama,
"nilai": nilai,
"threshold": self.threshold_high
})
if self.threshold_low and nilai < self.threshold_low:
self.emit("threshold_low", {
"sensor": self.nama,
"nilai": nilai,
"threshold": self.threshold_low
})
self.emit("data_update", {"sensor": self.nama, "nilai": nilai})
# --- Penggunaan ---
monitor_suhu = SensorMonitor("Suhu", threshold_high=35, threshold_low=15)
monitor_gas = SensorMonitor("Gas MQ-135", threshold_high=1000)
# Daftarkan listener
def alert_handler(data):
print(f"🚨 ALERT: {data['sensor']} = {data['nilai']} (threshold: {data['threshold']})")
def log_handler(data):
print(f"📝 LOG: {data['sensor']} → {data['nilai']}")
def dashboard_handler(data):
print(f"📊 DASHBOARD: Update {data['sensor']}: {data['nilai']}")
monitor_suhu.on("threshold_high", alert_handler)
monitor_suhu.on("threshold_low", alert_handler)
monitor_suhu.on("data_update", log_handler)
monitor_suhu.on("data_update", dashboard_handler)
monitor_gas.on("threshold_high", alert_handler)
# Simulasi update
monitor_suhu.update(28) # Normal
monitor_suhu.update(36.5) # Threshold high triggered!
monitor_suhu.update(12) # Threshold low triggered!
monitor_gas.update(1500) # Gas bahaya!
8.3 — Factory Pattern
Membuat object tanpa perlu tahu class spesifiknya — cocok untuk membuat sensor berdasarkan konfigurasi file.
"""
Factory Pattern untuk Membuat Sensor dari Config
BeebaneLabs - https://beebanelabs.pages.dev
"""
import json
class SensorFactory:
"""Factory untuk membuat object sensor dari konfigurasi."""
_registry = {}
@classmethod
def register(cls, tipe, class_ref):
"""Mendaftarkan tipe sensor ke factory."""
cls._registry[tipe] = class_ref
@classmethod
def buat(cls, config):
"""Membuat sensor dari dict konfigurasi."""
tipe = config.get("tipe")
if tipe not in cls._registry:
raise ValueError(f"Tipe sensor tidak dikenal: {tipe}")
sensor_class = cls._registry[tipe]
return sensor_class(**{k: v for k, v in config.items() if k != "tipe"})
@classmethod
def buat_dari_file(cls, filepath):
"""Membuat semua sensor dari file JSON config."""
with open(filepath, "r") as f:
configs = json.load(f)
sensors = []
for cfg in configs:
sensor = cls.buat(cfg)
sensors.append(sensor)
return sensors
# Register semua tipe sensor
# SensorFactory.register("dht22", SensorDHT)
# SensorFactory.register("ultrasonik", SensorUltrasonik)
# SensorFactory.register("ldr", SensorLDR)
# Contoh config JSON:
# [
# {"tipe": "dht22", "nama": "Suhu Ruang", "pin": 4},
# {"tipe": "ultrasonik", "nama": "Jarak Tangki", "pin_trigger": 17, "pin_echo": 27},
# {"tipe": "ldr", "nama": "Cahaya Taman", "pin": 22}
# ]
| Pattern | Kegunaan di IoT |
|---|---|
| Singleton | MQTT client, database connection, config manager — hanya satu instance |
| Observer | Event-driven notifications — sensor threshold, alert system |
| Factory | Membuat sensor/aktuator dari konfigurasi — fleksibel dan scalable |
| Strategy | Ganti algoritma (filter data, format payload) secara dinamis |
| State | Menangani state perangkat (idle, running, error, sleep) |
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang OOP dalam Python untuk IoT: