Pendahuluan — OOP yang sama, RAM yang beda

Di Seri 3 kamu sudah nyaman dengan class, composition, dan pattern ringan di Factory & Strategy (#50) serta Capstone (#49). Artikel ini jembatan ke perangkat: MicroPython di ESP32 — OOP-nya tetap Python, tapi memori dan modul hardware memaksa desain lebih hemat.

Pola yang sama dengan firmware yang membesar di capstone smart greenhouse (#39): layanan punya sensor dan aktuator, bukan satu file spaghetti GPIO.

Prasyarat: Factory & Strategy (#50) atau minimal Composition (#47) + Mengenal OOP (#40). Familiar ESP32: Mengenal ESP32 (#1) · Greenhouse (#39). Opsional: Python di ekosistem Kindo lewat Subscriber MQTT (#18).

CPython vs MicroPython — apa yang berubah?

Aspek CPython (Seri 3) MicroPython (ESP32)
RAM / flash Longgar Ketat — hindari object berlebihan
@dataclass Stdlib lengkap Sering tidak ada / terbatas — class biasa lebih aman
Hardware Tidak ada machine.Pin, ADC, I2C, WiFi
OOP inti class, inheritance, composition Sama — mental model Seri 3 tetap berlaku

Jangan buang composition hanya karena “firmware”. Justru di node IoT, Composition (#47) menjaga GPIO tidak tercecer di mana-mana.

Node IoT — composition, bukan spaghetti GPIO Node Sensor Led punya punya
Node punya Sensor dan Led — pola yang sama dengan “service punya koleksi” di Seri 3.

Stub PC — belajar tanpa board di tangan

Contoh di bawah memakai stub FakePin agar bisa dijalankan di PC (CPython) untuk latihan. Di ESP32 ganti menjadi from machine import Pin. Ini sengaja: audit & belajar tidak bergantung pada hardware.

class FakePin:
    """Pengganti machine.Pin untuk latihan di PC."""

    OUT = 1
    IN = 0

    def __init__(self, nomor, mode=OUT):
        self.nomor = nomor
        self.mode = mode
        self._nilai = 0

    def value(self, v=None):
        if v is None:
            return self._nilai
        self._nilai = 1 if v else 0
        return self._nilai


pin = FakePin(2, FakePin.OUT)
pin.value(1)
print("pin", pin.nomor, "=>", pin.value())

Output:

pin 2 => 1

Class Led — encapsulate GPIO

Jangan sebar pin.value(1) di loop utama. Bungkus di class (semangat Encapsulation (#43)):

class FakePin:
    OUT = 1

    def __init__(self, nomor, mode=OUT):
        self.nomor = nomor
        self._nilai = 0

    def value(self, v=None):
        if v is None:
            return self._nilai
        self._nilai = 1 if v else 0
        return self._nilai


class Led:
    def __init__(self, pin):
        self._pin = pin

    def nyala(self):
        self._pin.value(1)

    def mati(self):
        self._pin.value(0)

    def status(self):
        return "ON" if self._pin.value() else "OFF"


led = Led(FakePin(2))
led.nyala()
print(led.status())
led.mati()
print(led.status())

Output:

ON
OFF

Class Sensor — baca + label

Sensor di MicroPython sering ADC atau DHT. Untuk latihan PC kita simulasi nilai:

class SensorSuhu:
    def __init__(self, nama, baca_fn):
        self.nama = nama
        self._baca_fn = baca_fn

    def baca(self):
        return float(self._baca_fn())

    def label(self, nilai=None):
        if nilai is None:
            nilai = self.baca()
        return f"{self.nama}: {nilai:.1f} C"


def simulasi_dht():
    return 27.5


s = SensorSuhu("DHT22", simulasi_dht)
print(s.label())

Output:

DHT22: 27.5 C

Parameter opsional nilai disiapkan sejak dini: nanti di Node.tick() kita teruskan hasil satu bacaan agar sensor tidak dibaca dua kali. Di board nyata, baca_fn bisa memanggil driver DHT — pola callback/composition ini selaras dengan “satu antarmuka, banyak bentuk” di Polymorphism (#45).

Node — composition di firmware

Node tidak mewarisi Led; ia punya sensor dan led:

class FakePin:
    OUT = 1

    def __init__(self, nomor, mode=OUT):
        self.nomor = nomor
        self.mode = mode
        self._nilai = 0

    def value(self, v=None):
        if v is None:
            return self._nilai
        self._nilai = 1 if v else 0
        return self._nilai


class Led:
    def __init__(self, pin):
        self._pin = pin

    def nyala(self):
        self._pin.value(1)

    def mati(self):
        self._pin.value(0)

    def status(self):
        return "ON" if self._pin.value() else "OFF"


class SensorSuhu:
    def __init__(self, nama, baca_fn):
        self.nama = nama
        self._baca_fn = baca_fn

    def baca(self):
        return float(self._baca_fn())

    def label(self, nilai=None):
        # Jangan panggil baca() lagi jika nilai sudah ada —
        # penting saat baca_fn mahal / side-effect (pop, I2C).
        if nilai is None:
            nilai = self.baca()
        return f"{self.nama}: {nilai:.1f} C"


class Node:
    def __init__(self, nama, sensor, led, ambang=30.0):
        self.nama = nama
        self.sensor = sensor
        self.led = led
        self.ambang = ambang

    def tick(self):
        suhu = self.sensor.baca()
        if suhu >= self.ambang:
            self.led.nyala()
        else:
            self.led.mati()
        return f"{self.nama} | {self.sensor.label(suhu)} | LED {self.led.status()}"


def baca():
    return 31.2


node = Node("Kebun-A", SensorSuhu("DHT22", baca), Led(FakePin(2)), ambang=30.0)
print(node.tick())

Output:

Kebun-A | DHT22: 31.2 C | LED ON

Satu tick() = satu baca(). Label memakai nilai yang sudah dibaca, bukan memanggil sensor lagi.

Porting singkat ke ESP32

Setelah demo() hijau di PC, flash ke board. Ganti stub dengan modul hardware — antarmuka class Led/Node tetap sama (semangat Encapsulation (#43)):

# MicroPython di ESP32
from machine import Pin

led = Led(Pin(2, Pin.OUT))
# SensorSuhu: isi baca_fn dengan driver DHT/ADC kamu
# node = Node("Kebun-A", sensor, led, ambang=30.0)
# while True:
#     print(node.tick())

Jangan biarkan nomor GPIO tersebar di loop utama. Kalau ImportError: machine muncul di desktop, itu tanda kamu menjalankan kode board di CPython — kembali ke FakePin untuk latihan.

Pola Dasar — OOP di node MicroPython

  1. 1
    Bungkus pin di class kecil Led/Sensor — jangan biarkan nomor GPIO tersebar.
  2. 2
    Susun Node dengan composition Node punya sensor + aktuator — pola Composition (#47).
  3. 3
    Hemat fitur bahasa Class biasa dulu; dataclass (#48) / ABC (#46) berat boleh ditunda di MCU kecil.
  4. 4
    Uji di PC dengan stub FakePin / fungsi simulasi — ganti ke machine saat flash.
  5. 5
    Hubungkan ke Seri IoT MQTT/dashboard: lihat Greenhouse (#39) dan Deep Sleep DHT22 (#11).

Kode lengkap — node_micropython_oop.py

Simpan dan jalankan di PC: python node_micropython_oop.py. Di ESP32, ganti FakePin dengan machine.Pin (lihat komentar).

"""OOP ringan untuk node IoT — stub PC (Tier 2 #51).
Di ESP32: from machine import Pin  # lalu Pin(2, Pin.OUT)
"""

from __future__ import annotations


class FakePin:
    OUT = 1
    IN = 0

    def __init__(self, nomor: int, mode: int = OUT) -> None:
        self.nomor = nomor
        self.mode = mode
        self._nilai = 0

    def value(self, v=None):
        if v is None:
            return self._nilai
        self._nilai = 1 if v else 0
        return self._nilai


class Led:
    def __init__(self, pin: FakePin) -> None:
        self._pin = pin

    def nyala(self) -> None:
        self._pin.value(1)

    def mati(self) -> None:
        self._pin.value(0)

    def status(self) -> str:
        return "ON" if self._pin.value() else "OFF"


class SensorSuhu:
    def __init__(self, nama: str, baca_fn) -> None:
        self.nama = nama
        self._baca_fn = baca_fn

    def baca(self) -> float:
        return float(self._baca_fn())

    def label(self, nilai: float | None = None) -> str:
        if nilai is None:
            nilai = self.baca()
        return f"{self.nama}: {nilai:.1f} C"


class Node:
    def __init__(self, nama: str, sensor: SensorSuhu, led: Led, ambang: float = 30.0) -> None:
        self.nama = nama
        self.sensor = sensor
        self.led = led
        self.ambang = ambang

    def tick(self) -> str:
        suhu = self.sensor.baca()
        if suhu >= self.ambang:
            self.led.nyala()
        else:
            self.led.mati()
        return f"{self.nama} | {self.sensor.label(suhu)} | LED {self.led.status()}"


def demo() -> None:
    nilai = [28.0, 31.5, 29.0]

    def baca():
        return nilai.pop(0) if nilai else 29.0

    node = Node("Kebun-A", SensorSuhu("DHT22", baca), Led(FakePin(2)), ambang=30.0)
    for _ in range(3):
        print(node.tick())


if __name__ == "__main__":
    demo()

Output yang diharapkan:

Kebun-A | DHT22: 28.0 C | LED OFF
Kebun-A | DHT22: 31.5 C | LED ON
Kebun-A | DHT22: 29.0 C | LED OFF

Kesalahan umum

Gejala Penyebab tipikal Perbaikan
MemoryError di MCU Terlalu banyak object / string besar Kurangi alokasi; class tipis; hindari import berat
GPIO “aneh” di banyak file Nomor pin hardcode tersebar Bungkus di Led/Sensor; Node yang orkestrasi
ImportError: machine di PC Menjalankan kode ESP32 di CPython Pakai stub FakePin saat belajar di desktop
Memaksa dataclass/ABC penuh Port MicroPython terbatas Class biasa + composition cukup untuk node kecil
Warisi Node(Led) Salah inheritance Composition: Node punya Led
Nilai sensor “loncat” / LED salah label() memanggil baca() lagi di dalam tick() Satu bacaan per tick — teruskan nilai ke label(suhu)

Latihan singkat

  1. Tambah class Relay mirip Led (semangat Encapsulation (#43)), lalu masukkan ke Node (composition).
  2. Ubah demo() agar LED nyala hanya jika suhu > ambang dan dua tick berturut-turut panas.
  3. Sketsa: bagaimana Node.tick() bisa menerbitkan JSON ke MQTT (lihat pola topik di Deep Sleep (#11)) — tanpa wajib implementasi penuh.

FAQ singkat

Apakah harus MicroPython, bukan Arduino C++?
Tidak wajib. Artikel ini untuk pembaca Seri 3 yang ingin OOP Python di board. Firmware C++ tetap valid di Seri ESP32.

Apakah Factory/Strategy (#50) dipakai di MCU?
Boleh jika cabang jenis sensor mulai liar. Untuk satu DHT + satu LED, composition sederhana lebih dulu.

Type hint float | None aman di ESP32?
Blok lengkap memakai from __future__ import annotations untuk latihan di PC (CPython 3.10+). Saat flash MicroPython, type hint boleh dihapus — perilaku runtime tidak bergantung padanya.

Di mana WiFi/MQTT?
Di luar scope singkat ini. Sambungkan ke alur Greenhouse (#39) setelah struktur class rapi.

Lanjut ke mana?
Lanjut Tier 2: Dari OOP ke Web — Class di Flask / FastAPI (#52) — service + handler tipis sebagai pintu Web Dev.

Kesimpulan & langkah berikutnya

OOP Seri 3 tidak hilang di MicroPython — yang berubah adalah anggaran memori dan akses hardware. Bungkus GPIO, susun Node dengan composition, uji di PC dengan stub, lalu flash ke ESP32.

Artikel ini adalah #51 (ini) — jembatan Tier 2 setelah Factory & Strategy (#50), mengarah ke Seri IoT. Lanjut web: Flask / FastAPI (#52).

Tier 2 progress: langkah #51 (ini) · Factory (#50) LIVE · Flask/FastAPI (#52) · Seri 3 tetap 10/10. Prasyarat: Factory (#50) · Composition (#47) · Capstone (#49) · OOP (#40) · Greenhouse (#39) · ESP32 (#1).