Monitoring Teknologi Stop Kontak Pintar Dengan Sensor PZEM-004T Berbasis Raspberry Pi Pico

MONITORING TEKNOLOGI STOP KONTAK PINTAR DENGAN SENSOR PZEM-004T BERBASIS RASPBERRY PI PICO

Aldino Sultansyah1, Frisca Syaharani2, Muhammad Satrio Adhy3, Salsabila Chairunisa4

Jurusan Teknik Elektro, Prodi Teknologi Rekayasa Elektronika, Politeknik Negeri Semarang
Jl. Prof. Soedarto, Tembalang, Kec. Tembalang, Kota Semarang, Jawa Tengah, 50275
1aldino.43423003@mhs.polines.ac.id, 2frisca.43423009@ mhs.polines.ac.id,  3satrio.43423015@mhs.polines.ac.id ,  4salsabila.43423022@mhs.polines.ac.id

Abstrak

Energi listrik merupakan kebutuhan vital dalam kehidupan sehari-hari dan sektor industri, namun alat ukur konvensional yang ada saat ini masih memiliki keterbatasan karena hanya menampilkan total konsumsi energi tanpa memberikan informasi penggunaan secara waktu nyata (real-time). Keterbatasan ini membuat pengguna kesulitan memantau parameter listrik seperti tegangan, arus, daya, energi, hingga biaya secara langsung. Untuk mengatasi permasalahan tersebut, proyek ini merancang dan mengimplementasikan sistem "Stop Kontak Pintar++" berbasis mikrokontroler Raspberry Pi Pico dan sensor PZEM-004T sebagai solusi monitoring energi listrik yang murah, akurat, dan informatif. Sistem ini dirancang untuk mengukur berbagai parameter kelistrikan meliputi Tegangan (V), Arus (A), Daya Aktif (W), Energi (kWh), Frekuensi (Hz), dan Power Factor secara real-time. Selain fungsi monitoring, alat ini juga mengintegrasikan sistem proteksi pintar berupa Over-Power Protection menggunakan modul relay 5V yang secara otomatis memutus aliran listrik jika beban yang terpasang melebihi batas yang diatur oleh pengguna. Data hasil pengukuran disajikan secara interaktif melalui layar LCD  dengan sistem navigasi tiga tombol fisik, serta mendukung fitur live streaming ke aplikasi dashboard di laptop untuk pemantauan grafik secara langsung dan pencetakan laporan ke format Excel (CSV). Berdasarkan hasil pengujian, Stop Kontak Pintar ini terbukti mampu memonitor berbagai parameter kelistrikan serta mengendalikan beban listrik dengan baik. Alat ini tidak hanya ramah pengguna, tetapi juga memiliki potensi besar untuk dikembangkan lebih lanjut pada aplikasi rumah pintar (smart home), manajemen energi, dan peningkatan efisiensi penggunaan listrik di masa depan.

Kata kunci: Stop Kontak Pintar, Raspberry Pi Pico, Sensor PZEM-004T, Real-time Monitoring, Over-Power Protection.

 

I.  PENDAHULUAN

A. LATAR BELAKANG

Energi listrik telah menjadi kebutuhan vital dalam kehidupan sehari-hari dan industri. Namun, alat ukur konvensional yang ada saat ini masih sangat terbatas karena hanya menampilkan total akumulasi konsumsi energi. Akibatnya, pengguna kesulitan untuk memantau fluktuasi parameter penting seperti tegangan, arus, daya, dan estimasi biaya pemakaian secara waktu nyata (real-time). Ketiadaan informasi ini membuat pola penggunaan listrik menjadi tidak terkontrol dan menyulitkan deteksi dini terhadap pemborosan energi.

Selain masalah transparansi data, stop kontak biasa juga belum memiliki sistem proteksi mandiri yang adaptif. Ketika beban elektronik yang terpasang melebihi kapasitas, risiko terjadinya panas berlebih (overheating) hingga korsleting listrik menjadi sangat tinggi. Oleh karena itu, diperlukan sebuah inovasi sistem monitoring energi listrik yang murah, akurat, informatif, sekaligus memiliki fitur proteksi aktif.

Melalui proyek Stop Kontak Pintar++, kendala tersebut coba diselesaikan. Menggunakan Raspberry Pi Pico sebagai pengendali utama dan sensor PZEM-004T, alat ini mampu mengukur parameter kelistrikan secara presisi. Sistem ini juga dilengkapi dengan modul relay untuk memutus aliran secara otomatis jika terjadi kelebihan beban (Over-Power Protection), layar LCD lokal yang interaktif, serta dashboard laptop untuk memantau grafik pemakaian dan mencetak laporan langsung ke format Excel.

B. RUMUSAN MASALAH

Berdasarkan uraian di atas, terdapat beberapa perumusan masalah yang harus
diperhatikan, yaitu:

1.      Bagaimana cara mengukur dan memantau parameter kelistrikan (tegangan, arus, daya, energi, frekuensi, dan power factor) secara waktu nyata (real-time)?

2.      Bagaimana cara mengintegrasikan sistem proteksi otomatis jika terjadi penggunaan daya listrik yang melebihi batas (over-power)?

3.      Bagaimana menampilkan data pemantauan listrik agar mudah dipahami secara lokal sekaligus dapat dianalisis dalam bentuk grafik dan laporan di laptop?

 

C. BATASAN MASALAH

Dalam pembuatan projek ini terdapat Batasan masalah terhadap sistem ini, yaitu:

1.      Pengendali utama yang digunakan terbatas pada mikrokontroler Raspberry Pi Pico.

2.      Sensor yang digunakan untuk membaca parameter kelistrikan khusus menggunakan PZEM-004T.

3.      Parameter yang diukur terbatas pada Tegangan (V), Arus (A), Daya Aktif (W), Energi (kWh), Frekuensi (Hz), dan Power Factor.

4.      Tampilan lokal menggunakan layar LCD 16x2 dengan navigasi 3 tombol fisik, sedangkan monitoring grafis dan ekspor laporan (format Excel/CSV) memerlukan koneksi ke aplikasi dashboard di laptop.

5.      Sistem proteksi otomatis hanya bekerja memutus aliran listrik menggunakan modul relay 5V berdasarkan batas daya (Over-Power Protection) yang diatur pengguna.


D. TUJUAN

Tujuan dari pembuatan projek ini yaitu:

1.      Monitoring Parameter Listrik: Mampu mengukur Tegangan, Arus, Daya Aktif, Energi, Frekuensi, dan Power Factor secara real-time.

2.      Sistem Proteksi Pintar: Menyediakan perlindungan otomatis (Over-Power Protection) dengan memutus aliran listrik melalui relay jika daya melebihi batas.

3.      Antarmuka Interaktif: Menyajikan data ke dalam tampilan LCD 16x2 lokal yang mudah dipahami dengan navigasi tiga tombol.

4.      Perekaman & Analisis Data: Mendukung fitur live streaming ke aplikasi dashboard laptop untuk pemantauan grafik secara langsung dan pencetakan data ke format Excel (CSV).


II. METODOLOGI

Metodologi proyek ini diawali dengan analisis kebutuhan komponen untuk mengintegrasikan Raspberry Pi Pico, sensor PZEM-004T, modul relay 5V, LCD 16x2 I2C, logic shifter, dan tiga tombol fisik. Tahap berikutnya adalah perancangan perangkat keras (hardware), di mana aliran listrik PLN dihubungkan ke sensor PZEM-004T untuk diukur parameternya, lalu diteruskan ke beban lampu melalui relay. Penggunaan logic shifter diwajibkan untuk menjembatani perbedaan level tegangan logika antara Raspberry Pi Pico (3.3V) dengan komponen 5V lainnya. Selanjutnya, dilakukan perancangan perangkat lunak (software) pada mikrokontroler untuk mengolah data sensor melalui komunikasi UART, mengatur sistem navigasi menu pada LCD, serta menjalankan algoritma proteksi otomatis (Over-Power Protection). Bersamaan dengan itu, dirancang aplikasi dashboard pada laptop untuk menangkap aliran data via USB, memvisualisasikannya ke dalam grafik secara langsung, dan mengekspor laporan ke format Excel. Tahap akhir diakhiri dengan pengujian fungsionalitas dan integrasi menyeluruh untuk memastikan akurasi pembacaan alat, kecepatan respons proteksi relay, serta kestabilan live streaming data.


III. KAJIAN PUSTAKA

Pembahasan dalam bagian ini meliputi perancangan dan komponen apa saja yang digunakan dalam projek ini.

A.    Sensor PZEM-004T

Sensor PZEM-004T berfungsi sebagai modul pengukur parameter kelistrikan utama yang terhubung langsung dengan sumber listrik PLN (AC 220V 50 Hz). Sensor ini mampu mengukur secara simultan berbagai parameter penting seperti Tegangan (V), Arus (A), Daya Aktif (W), Energi (kWh), Frekuensi (Hz), dan Power Factor secara waktu nyata (real-time). Data hasil pengukuran dikirim ke pengendali utama menggunakan protokol komunikasi UART (TTL) berlevel logika 5V.

Gambar 1. Sensor PZEM-004T

B.     Raspberry Pi Pico

Raspberry Pi Pico digunakan sebagai unit pengendali utama (mikrokontroler) dalam proyek ini. Perangkat ini bertanggung jawab untuk menerima data mentah dari sensor melalui komunikasi serial, mengolah algoritma pembatas daya (Over-Power Protection), mengatur navigasi menu input, serta mengontrol keluaran data ke LCD dan aplikasi dashboard di laptop. Komponen ini bekerja dengan level tegangan logika 3.3V pada pin GPIO-nya.

Gambar 2. Raspberry Pi Pico

C.     Relay Modul 5V

Modul relay 5V berperan sebagai sakelar elektronik yang dikendalikan langsung oleh mikrokontroler untuk memutus atau menghubungkan aliran listrik menuju beban (lampu/perangkat elektronik). Dalam sistem ini, relay bertindak sebagai aktuator utama untuk sistem proteksi pintar (Over-Power Protection) yang akan aktif secara otomatis demi mencegah terjadinya panas berlebih (overheating) atau hubungan pendek arus listrik.

Mengupload: 96348 dari 96348 byte diupload.

Gambar 3. Relay Modul 5V

D.    LCD I2C

Layar LCD  digunakan sebagai komponen keluaran visual utama pada perangkat fisik. Berkat integrasi dengan modul komunikasi I2C, penampil data lokal ini hanya membutuhkan sedikit pin mikrokontroler untuk menampilkan karakter informasi parameter listrik yang kompleks secara interaktif, mudah dipahami, dan dinamis sesuai menu yang dipilih melalui tombol fisik.

Gambar 4. LCD I2C

E.     Push Button

Tombol fisik atau push button dalam proyek ini berjumlah tiga buah dan dipasang sebagai komponen masukan (input user). Tombol-tombol ini berfungsi sebagai pemilih menu (menu navigator) lokal, yang memungkinkan pengguna untuk berinteraksi dengan alat, beralih tampilan informasi pada layar, serta mengatur konfigurasi batas daya maksimum (threshold) secara manual.

Gambar 5. Push Button

F. Diagram Blok

Gambar 6. Diagram Blok

G. Diagram Alir

Gambar 7. Diagram Alir

H. Diagram Skematik

Gambar 8. Diagram Skematik

I. Program

a.       Program Main.py

# Memanggil modul bawaan untuk kontrol Pin, Serial (UART), dan I2C

from machine import Pin, UART, I2C

# Memanggil modul waktu untuk jeda (delay) dan perhitungan waktu (milidetik)

import time

# Memanggil modul OS untuk mengelola file sistem (membuat/menyimpan file csv)

import os

# Memanggil pustaka eksternal untuk mengendalikan layar LCD via I2C

from pico_i2c_lcd import I2cLcd

 

# ================= PENGATURAN PIN & KOMUNIKASI =================

# Mengaktifkan jalur I2C0 di pin GP0 (SDA) dan GP1 (SCL) dengan kecepatan 400kHz

i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)

 

# Mengaktifkan jalur UART1 di pin GP4 (TX) dan GP5 (RX) dengan baudrate 9600 untuk PZEM

uart = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))

 

# Mengatur pin tombol sebagai INPUT dengan Internal PULL_UP (default HIGH, ditekan jadi LOW)

btn_ok = Pin(6, Pin.IN, Pin.PULL_UP)      

btn_kiri = Pin(12, Pin.IN, Pin.PULL_UP)   

btn_kanan = Pin(7, Pin.IN, Pin.PULL_UP)   

 

# Mengatur pin relay dan buzzer sebagai OUTPUT

relay = Pin(16, Pin.OUT)

buzzer = Pin(15, Pin.OUT)

 

# Memastikan relay dalam kondisi mati (0) saat program pertama kali berjalan

relay.value(0)

 

# ================= INISIALISASI LCD =================

# Mencari alamat perangkat I2C yang terhubung ke Pico

devices = i2c.scan()

# Jika ada perangkat I2C yang ditemukan (panjang list tidak nol)

if len(devices) != 0:

    # Mendeklarasikan objek LCD dengan alamat yang ditemukan, ukuran 2 baris x 16 kolom

    lcd = I2cLcd(i2c, devices[0], 2, 16)

    # Membersihkan layar LCD dari karakter acak

    lcd.clear()

    # Menampilkan teks awalan

    lcd.putstr("Sistem Memulai..")

    # Jeda 1 detik agar tulisan bisa dibaca

    time.sleep(1)

    # Bersihkan layar kembali untuk bersiap masuk ke menu utama

    lcd.clear()

 

# ================= FUNGSI BACA PZEM =================

def read_pzem():

    # Deretan byte Modbus RTU untuk meminta data (Read Input Register) ke PZEM

    req = b'\x01\x04\x00\x00\x00\x0A\x70\x0D'

    # Mengirim perintah tersebut melalui jalur Serial/UART

    uart.write(req)

    # Jeda 0.2 detik memberi waktu PZEM untuk memproses dan membalas data

    time.sleep(0.2)

   

    # Mengecek apakah ada data balasan yang masuk di buffer UART

    if uart.any():

        # Membaca seluruh data balasan dan menyimpannya di variabel 'resp'

        resp = uart.read()

        # Memastikan balasan komplit (minimal 25 byte sesuai spesifikasi PZEM)

        if len(resp) >= 25:

            # Menggabungkan High Byte dan Low Byte lalu membaginya sesuai resolusi sensor

            voltage = (resp[3] << 8 | resp[4]) / 10.0      # Resolusi 0.1 V

            current = (resp[5] << 8 | resp[6]) / 1000.0    # Resolusi 0.001 A

            power = (resp[9] << 8 | resp[10]) / 10.0       # Resolusi 0.1 W

            energy = (resp[13] << 8 | resp[14])            # Resolusi 1 Wh

            # Mengembalikan ke-4 nilai tersebut ke program utama

            return voltage, current, power, energy

    # Jika gagal membaca/tidak ada alat, kembalikan nilai kosong (None)

    return None, None, None, None

 

# ================= FUNGSI BANTUAN (UTILITY) =================

def beep():

    """Membunyikan buzzer selama 0.05 detik sebagai efek suara tombol"""

    buzzer.value(1)       # Nyalakan buzzer

    time.sleep(0.05)      # Tunggu 50 milidetik

    buzzer.value(0)       # Matikan buzzer

 

def tunggu_lepas(btn):

    """Mencegah double-click dengan menahan program selama tombol masih ditekan"""

    # Selama tombol masih bernilai 0 (ditekan), program akan berputar di sini

    while btn.value() == 0:

        time.sleep(0.05)  # Cek setiap 50 milidetik

    # Beri sedikit jeda ekstra setelah tombol dilepas agar stabil (debouncing)

    time.sleep(0.05)

 

# ================= VARIABEL SISTEM UI & KONTROL =================

# Daftar menu yang akan ditampilkan di layar

menu_list = ["1. Parameter", "2. Relay", "3. Batas Daya", "4. Streaming", "5. Info Sistem"]

# Indeks untuk melacak menu mana yang sedang dipilih (dimulai dari 0)

menu_index = 0

 

# Penanda apakah kita sedang di daftar menu (True) atau di dalam isi menu (False)

in_menu = True

# Penanda halaman untuk membagi tampilan sensor (0: Tegangan/Arus, 1: Daya/Energi)

param_page = 0

# Penanda status hidup/mati relay internal (default mati)

relay_status = False

 

# Variabel Sensor & Telemetri

last_pzem_read = 0       # Menyimpan catatan waktu terakhir sensor dibaca

v, i, p, e = 0, 0, 0, 0  # Variabel penampung nilai sensor terakhir

power_limit = 0          # Batas daya proteksi, 0 berarti proteksi OFF

is_streaming = False     # Penanda apakah mode kirim data live ke laptop sedang aktif

 

# ================= LOOP UTAMA =================

while True: # Program akan berjalan terus menerus di dalam blok ini

   

    # ---------------- 1. BACA SENSOR & PROTEKSI REAL-TIME ----------------

    # Jika selisih waktu sekarang dan pembacaan terakhir lebih dari 1000ms (1 detik)

    if time.ticks_ms() - last_pzem_read > 1000:

        # Panggil fungsi baca sensor, simpan hasilnya ke variabel sementara

        temp_v, temp_i, temp_p, temp_e = read_pzem()

        # Jika hasilnya valid (bukan None)

        if temp_v is not None:

            # Perbarui variabel utama dengan nilai terbaru

            v, i, p, e = temp_v, temp_i, temp_p, temp_e

           

            # --- LOGIKA STREAMING LIVE KE LAPTOP ---

            # Jika mode streaming aktif (ON)

            if is_streaming:

                # Kirim data ke port Serial USB laptop, format CSV (dipisah koma)

                print(f"{v},{i},{p},{e}")

           

            # --- LOGIKA PROTEKSI OVER-POWER ---

            # Jika batas daya tidak 0, DAN relay sedang ON, DAN daya (p) melebihi batas

            if power_limit > 0 and relay_status == True and p > power_limit:

                # Paksa status relay menjadi False

                relay_status = False

                # Matikan sakelar relay secara fisik

                relay.value(0)

                # Bunyikan alarm peringatan sebanyak 3 kali berturut-turut

                for _ in range(3):

                    beep()

                    time.sleep(0.1)

                   

        # Catat waktu saat ini sebagai waktu pembacaan terakhir

        last_pzem_read = time.ticks_ms()

 

    # ---------------- 2. BACA INPUT TOMBOL ----------------

    # JIKA TOMBOL KIRI DITEKAN

    if btn_kiri.value() == 0:

        beep() # Bunyikan suara

        if in_menu:

            # Geser pilihan menu ke kiri (berputar kembali ke belakang jika < 0)

            menu_index = (menu_index - 1) % len(menu_list)

        else:

            # Aksi tombol kiri di dalam menu spesifik

            if menu_index == 0:

                param_page = 0 # Di Menu Parameter: Tampilkan Tegangan & Arus

            elif menu_index == 1:

                # Di Menu Relay: Ubah status kebalikannya (ON jadi OFF, OFF jadi ON)

                relay_status = not relay_status

                relay.value(1 if relay_status else 0) # Update fisik relay

            elif menu_index == 2:

                # Di Menu Batas Daya: Kurangi batas sebanyak 10W (tidak boleh kurang dari 0)

                power_limit = max(0, power_limit - 10)

            elif menu_index == 3:

                # Di Menu Streaming: Tombol Kiri berfungsi menyalakan/mematikan Live Data

                is_streaming = not is_streaming

                lcd.clear()

                if is_streaming:

                    lcd.putstr("Live Stream: ON ")

                    print("---START---") # Tanda mulai di laptop

                else:

                    lcd.putstr("Live Stream: OFF")

                    print("---END---")   # Tanda berhenti di laptop

                time.sleep(1) # Tahan layar 1 detik agar tulisan terbaca

               

        # Tunggu sampai tombol benar-benar dilepas, lalu bersihkan layar

        tunggu_lepas(btn_kiri)

        lcd.clear()

 

    # JIKA TOMBOL KANAN DITEKAN

    if btn_kanan.value() == 0:

        beep()

        if in_menu:

            # Geser pilihan menu ke kanan (berputar kembali ke awal jika melewati batas)

            menu_index = (menu_index + 1) % len(menu_list)

        else:

            # Aksi tombol kanan di dalam menu spesifik

            if menu_index == 0:

                param_page = 1 # Di Menu Parameter: Tampilkan Daya & Energi

            elif menu_index == 1:

                # Di Menu Relay: Lakukan hal yang sama seperti tombol kiri (Toggle)

                relay_status = not relay_status

                relay.value(1 if relay_status else 0)

            elif menu_index == 2:

                # Di Menu Batas Daya: Tambah batas sebanyak 10W (maksimal 2200W)

                power_limit = min(2200, power_limit + 10)

            elif menu_index == 3:

                # Di Menu Streaming: Tombol Kanan berfungsi menyimpan 1 baris log ke memori internal

                lcd.clear()

                lcd.putstr("Tersimpan!      ")

                try:

                    # Buka file "data_log.csv" dengan mode "a" (append = tambah di akhir baris)

                    with open("data_log.csv", "a") as file:

                        # Tulis nilai V, I, P, E dipisahkan koma, lalu diakhiri \n (baris baru)

                        file.write(f"{v},{i},{p},{e}\n")

                except OSError:

                    # Abaikan error jika gagal menyimpan (misal memori penuh)

                    pass

                time.sleep(1) # Tahan layar 1 detik

                lcd.clear()

               

        # Tunggu sampai tombol dilepas, lalu bersihkan layar

        tunggu_lepas(btn_kanan)

        lcd.clear()

 

    # JIKA TOMBOL OK DITEKAN

    if btn_ok.value() == 0:

        beep()

        # Balik keadaan variabel in_menu (Masuk menu jika di luar, keluar menu jika di dalam)

        in_menu = not in_menu

        tunggu_lepas(btn_ok)

        lcd.clear()

 

    # ---------------- 3. TAMPILKAN UI LCD ----------------

    # JIKA BERADA DI DAFTAR MENU

    if in_menu:

        lcd.move_to(0, 0) # Pindahkan kursor ke Baris 1, Kolom 1

        lcd.putstr("Pilih Menu:     ")

        lcd.move_to(0, 1) # Pindahkan kursor ke Baris 2, Kolom 1

        # Ambil nama menu dari list berdasarkan indeks, beri kurung siku

        teks_menu = f"<{menu_list[menu_index]}>"

        # Cetak dengan format spasi sisa agar memenuhi 16 kolom (menimpa teks lama)

        lcd.putstr(f"{teks_menu: <16}")

       

    # JIKA MASUK KE DALAM ISI MENU (MENGGUNAKAN TOMBOL OK)

    else:

        if menu_index == 0: # --- TAMPILAN MENU 1: PARAMETER ---

            if v is not None:

                if param_page == 0:

                    lcd.move_to(0, 0)

                    lcd.putstr(f"V: {v:.1f} V      ") # Format 1 angka desimal

                    lcd.move_to(0, 1)

                    lcd.putstr(f"I: {i:.2f} A      ") # Format 2 angka desimal

                else:

                    lcd.move_to(0, 0)

                    lcd.putstr(f"P: {p:.1f} W      ") # Format 1 angka desimal

                    lcd.move_to(0, 1)

                    lcd.putstr(f"E: {e} Wh       ")  # Tampilkan utuh (bulat)

            else:

                # Jika PZEM belum merespon

                lcd.move_to(0, 0)

                lcd.putstr("Menunggu PZEM...")

                lcd.move_to(0, 1)

                lcd.putstr("Tekan OK: Keluar")

               

        elif menu_index == 1: # --- TAMPILAN MENU 2: KONTROL RELAY ---

            lcd.move_to(0, 0)

            if relay_status:

                lcd.putstr("Status: ON      ")

            else:

                lcd.putstr("Status: OFF     ")

            lcd.move_to(0, 1)

            lcd.putstr("< Ubah >  OK:Kel") # Panduan tombol di baris kedua

           

        elif menu_index == 2: # --- TAMPILAN MENU 3: BATAS DAYA ---

            lcd.move_to(0, 0)

            if power_limit == 0:

                lcd.putstr("Limit: OFF      ")

            else:

                lcd.putstr(f"Limit: {power_limit} W      ")

            lcd.move_to(0, 1)

            lcd.putstr("<Kurang  Tambah>") # Panduan tombol di baris kedua

           

        elif menu_index == 3: # --- TAMPILAN MENU 4: STREAMING/LOGGER ---

            lcd.move_to(0, 0)

            if is_streaming:

                lcd.putstr("Status: LIVE OND") # Ada typo sedikit di 'OND', biasanya 'ON '

            else:

                lcd.putstr("Data Logger     ")

            lcd.move_to(0, 1)

            lcd.putstr("<Live    Simpan>") # Kiri untuk Live, Kanan untuk Simpan Log

           

        elif menu_index == 4: # --- TAMPILAN MENU 5: INFO SISTEM ---

            lcd.move_to(0, 0)

            lcd.putstr("Sistem v1.3     ") # Menampilkan versi sistem

            lcd.move_to(0, 1)

            lcd.putstr("Tekan OK: Keluar")

 

    # Memberikan sedikit waktu istirahat pada CPU Pico (100 milidetik per siklus)

    time.sleep(0.1)

b.      Program Monitoring.py

# Memanggil modul bawaan untuk kontrol Pin, Serial (UART), dan I2C

from machine import Pin, UART, I2C

# Memanggil modul waktu untuk jeda (delay) dan perhitungan waktu (milidetik)

import time

# Memanggil modul OS untuk mengelola file sistem (membuat/menyimpan file csv)

import os

# Memanggil pustaka eksternal untuk mengendalikan layar LCD via I2C

from pico_i2c_lcd import I2cLcd

 

# ================= PENGATURAN PIN & KOMUNIKASI =================

# Mengaktifkan jalur I2C0 di pin GP0 (SDA) dan GP1 (SCL) dengan kecepatan 400kHz

i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)

 

# Mengaktifkan jalur UART1 di pin GP4 (TX) dan GP5 (RX) dengan baudrate 9600 untuk PZEM

uart = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))

 

# Mengatur pin tombol sebagai INPUT dengan Internal PULL_UP (default HIGH, ditekan jadi LOW)

btn_ok = Pin(6, Pin.IN, Pin.PULL_UP)      

btn_kiri = Pin(12, Pin.IN, Pin.PULL_UP)   

btn_kanan = Pin(7, Pin.IN, Pin.PULL_UP)   

 

# Mengatur pin relay dan buzzer sebagai OUTPUT

relay = Pin(16, Pin.OUT)

buzzer = Pin(15, Pin.OUT)

 

# Memastikan relay dalam kondisi mati (0) saat program pertama kali berjalan

relay.value(0)

 

# ================= INISIALISASI LCD =================

# Mencari alamat perangkat I2C yang terhubung ke Pico

devices = i2c.scan()

# Jika ada perangkat I2C yang ditemukan (panjang list tidak nol)

if len(devices) != 0:

    # Mendeklarasikan objek LCD dengan alamat yang ditemukan, ukuran 2 baris x 16 kolom

    lcd = I2cLcd(i2c, devices[0], 2, 16)

    # Membersihkan layar LCD dari karakter acak

    lcd.clear()

    # Menampilkan teks awalan

    lcd.putstr("Sistem Memulai..")

    # Jeda 1 detik agar tulisan bisa dibaca

    time.sleep(1)

    # Bersihkan layar kembali untuk bersiap masuk ke menu utama

    lcd.clear()

 

# ================= FUNGSI BACA PZEM =================

def read_pzem():

    # Deretan byte Modbus RTU untuk meminta data (Read Input Register) ke PZEM

    req = b'\x01\x04\x00\x00\x00\x0A\x70\x0D'

    # Mengirim perintah tersebut melalui jalur Serial/UART

    uart.write(req)

    # Jeda 0.2 detik memberi waktu PZEM untuk memproses dan membalas data

    time.sleep(0.2)

   

    # Mengecek apakah ada data balasan yang masuk di buffer UART

    if uart.any():

        # Membaca seluruh data balasan dan menyimpannya di variabel 'resp'

        resp = uart.read()

        # Memastikan balasan komplit (minimal 25 byte sesuai spesifikasi PZEM)

        if len(resp) >= 25:

            # Menggabungkan High Byte dan Low Byte lalu membaginya sesuai resolusi sensor

            voltage = (resp[3] << 8 | resp[4]) / 10.0      # Resolusi 0.1 V

            current = (resp[5] << 8 | resp[6]) / 1000.0    # Resolusi 0.001 A

            power = (resp[9] << 8 | resp[10]) / 10.0       # Resolusi 0.1 W

            energy = (resp[13] << 8 | resp[14])            # Resolusi 1 Wh

            # Mengembalikan ke-4 nilai tersebut ke program utama

            return voltage, current, power, energy

    # Jika gagal membaca/tidak ada alat, kembalikan nilai kosong (None)

    return None, None, None, None

 

# ================= FUNGSI BANTUAN (UTILITY) =================

def beep():

    """Membunyikan buzzer selama 0.05 detik sebagai efek suara tombol"""

    buzzer.value(1)       # Nyalakan buzzer

    time.sleep(0.05)      # Tunggu 50 milidetik

    buzzer.value(0)       # Matikan buzzer

 

def tunggu_lepas(btn):

    """Mencegah double-click dengan menahan program selama tombol masih ditekan"""

    # Selama tombol masih bernilai 0 (ditekan), program akan berputar di sini

    while btn.value() == 0:

        time.sleep(0.05)  # Cek setiap 50 milidetik

    # Beri sedikit jeda ekstra setelah tombol dilepas agar stabil (debouncing)

    time.sleep(0.05)

 

# ================= VARIABEL SISTEM UI & KONTROL =================

# Daftar menu yang akan ditampilkan di layar

menu_list = ["1. Parameter", "2. Relay", "3. Batas Daya", "4. Streaming", "5. Info Sistem"]

# Indeks untuk melacak menu mana yang sedang dipilih (dimulai dari 0)

menu_index = 0

 

# Penanda apakah kita sedang di daftar menu (True) atau di dalam isi menu (False)

in_menu = True

# Penanda halaman untuk membagi tampilan sensor (0: Tegangan/Arus, 1: Daya/Energi)

param_page = 0

# Penanda status hidup/mati relay internal (default mati)

relay_status = False

 

# Variabel Sensor & Telemetri

last_pzem_read = 0       # Menyimpan catatan waktu terakhir sensor dibaca

v, i, p, e = 0, 0, 0, 0  # Variabel penampung nilai sensor terakhir

power_limit = 0          # Batas daya proteksi, 0 berarti proteksi OFF

is_streaming = False     # Penanda apakah mode kirim data live ke laptop sedang aktif

 

# ================= LOOP UTAMA =================

while True: # Program akan berjalan terus menerus di dalam blok ini

   

    # ---------------- 1. BACA SENSOR & PROTEKSI REAL-TIME ----------------

    # Jika selisih waktu sekarang dan pembacaan terakhir lebih dari 1000ms (1 detik)

    if time.ticks_ms() - last_pzem_read > 1000:

        # Panggil fungsi baca sensor, simpan hasilnya ke variabel sementara

        temp_v, temp_i, temp_p, temp_e = read_pzem()

        # Jika hasilnya valid (bukan None)

        if temp_v is not None:

            # Perbarui variabel utama dengan nilai terbaru

            v, i, p, e = temp_v, temp_i, temp_p, temp_e

           

            # --- LOGIKA STREAMING LIVE KE LAPTOP ---

            # Jika mode streaming aktif (ON)

            if is_streaming:

                # Kirim data ke port Serial USB laptop, format CSV (dipisah koma)

                print(f"{v},{i},{p},{e}")

           

            # --- LOGIKA PROTEKSI OVER-POWER ---

            # Jika batas daya tidak 0, DAN relay sedang ON, DAN daya (p) melebihi batas

            if power_limit > 0 and relay_status == True and p > power_limit:

                # Paksa status relay menjadi False

                relay_status = False

                # Matikan sakelar relay secara fisik

                relay.value(0)

                # Bunyikan alarm peringatan sebanyak 3 kali berturut-turut

                for _ in range(3):

                    beep()

                    time.sleep(0.1)

                   

        # Catat waktu saat ini sebagai waktu pembacaan terakhir

        last_pzem_read = time.ticks_ms()

 

    # ---------------- 2. BACA INPUT TOMBOL ----------------

    # JIKA TOMBOL KIRI DITEKAN

    if btn_kiri.value() == 0:

        beep() # Bunyikan suara

        if in_menu:

            # Geser pilihan menu ke kiri (berputar kembali ke belakang jika < 0)

            menu_index = (menu_index - 1) % len(menu_list)

        else:

            # Aksi tombol kiri di dalam menu spesifik

            if menu_index == 0:

                param_page = 0 # Di Menu Parameter: Tampilkan Tegangan & Arus

            elif menu_index == 1:

                # Di Menu Relay: Ubah status kebalikannya (ON jadi OFF, OFF jadi ON)

                relay_status = not relay_status

                relay.value(1 if relay_status else 0) # Update fisik relay

            elif menu_index == 2:

                # Di Menu Batas Daya: Kurangi batas sebanyak 10W (tidak boleh kurang dari 0)

                power_limit = max(0, power_limit - 10)

            elif menu_index == 3:

                # Di Menu Streaming: Tombol Kiri berfungsi menyalakan/mematikan Live Data

                is_streaming = not is_streaming

                lcd.clear()

                if is_streaming:

                    lcd.putstr("Live Stream: ON ")

                    print("---START---") # Tanda mulai di laptop

                else:

                    lcd.putstr("Live Stream: OFF")

                    print("---END---")   # Tanda berhenti di laptop

                time.sleep(1) # Tahan layar 1 detik agar tulisan terbaca

               

        # Tunggu sampai tombol benar-benar dilepas, lalu bersihkan layar

        tunggu_lepas(btn_kiri)

        lcd.clear()

 

    # JIKA TOMBOL KANAN DITEKAN

    if btn_kanan.value() == 0:

        beep()

        if in_menu:

            # Geser pilihan menu ke kanan (berputar kembali ke awal jika melewati batas)

            menu_index = (menu_index + 1) % len(menu_list)

        else:

            # Aksi tombol kanan di dalam menu spesifik

            if menu_index == 0:

                param_page = 1 # Di Menu Parameter: Tampilkan Daya & Energi

            elif menu_index == 1:

                # Di Menu Relay: Lakukan hal yang sama seperti tombol kiri (Toggle)

                relay_status = not relay_status

                relay.value(1 if relay_status else 0)

            elif menu_index == 2:

                # Di Menu Batas Daya: Tambah batas sebanyak 10W (maksimal 2200W)

                power_limit = min(2200, power_limit + 10)

            elif menu_index == 3:

                # Di Menu Streaming: Tombol Kanan berfungsi menyimpan 1 baris log ke memori internal

                lcd.clear()

                lcd.putstr("Tersimpan!      ")

                try:

                    # Buka file "data_log.csv" dengan mode "a" (append = tambah di akhir baris)

                    with open("data_log.csv", "a") as file:

                        # Tulis nilai V, I, P, E dipisahkan koma, lalu diakhiri \n (baris baru)

                        file.write(f"{v},{i},{p},{e}\n")

                except OSError:

                    # Abaikan error jika gagal menyimpan (misal memori penuh)

                    pass

                time.sleep(1) # Tahan layar 1 detik

                lcd.clear()

               

        # Tunggu sampai tombol dilepas, lalu bersihkan layar

        tunggu_lepas(btn_kanan)

        lcd.clear()

 

    # JIKA TOMBOL OK DITEKAN

    if btn_ok.value() == 0:

        beep()

        # Balik keadaan variabel in_menu (Masuk menu jika di luar, keluar menu jika di dalam)

        in_menu = not in_menu

        tunggu_lepas(btn_ok)

        lcd.clear()

 

    # ---------------- 3. TAMPILKAN UI LCD ----------------

    # JIKA BERADA DI DAFTAR MENU

    if in_menu:

        lcd.move_to(0, 0) # Pindahkan kursor ke Baris 1, Kolom 1

        lcd.putstr("Pilih Menu:     ")

        lcd.move_to(0, 1) # Pindahkan kursor ke Baris 2, Kolom 1

        # Ambil nama menu dari list berdasarkan indeks, beri kurung siku

        teks_menu = f"<{menu_list[menu_index]}>"

        # Cetak dengan format spasi sisa agar memenuhi 16 kolom (menimpa teks lama)

        lcd.putstr(f"{teks_menu: <16}")

       

    # JIKA MASUK KE DALAM ISI MENU (MENGGUNAKAN TOMBOL OK)

    else:

        if menu_index == 0: # --- TAMPILAN MENU 1: PARAMETER ---

            if v is not None:

                if param_page == 0:

                    lcd.move_to(0, 0)

                    lcd.putstr(f"V: {v:.1f} V      ") # Format 1 angka desimal

                    lcd.move_to(0, 1)

                    lcd.putstr(f"I: {i:.2f} A      ") # Format 2 angka desimal

                else:

                    lcd.move_to(0, 0)

                    lcd.putstr(f"P: {p:.1f} W      ") # Format 1 angka desimal

                    lcd.move_to(0, 1)

                    lcd.putstr(f"E: {e} Wh       ")  # Tampilkan utuh (bulat)

            else:

                # Jika PZEM belum merespon

                lcd.move_to(0, 0)

                lcd.putstr("Menunggu PZEM...")

                lcd.move_to(0, 1)

                lcd.putstr("Tekan OK: Keluar")

               

        elif menu_index == 1: # --- TAMPILAN MENU 2: KONTROL RELAY ---

            lcd.move_to(0, 0)

            if relay_status:

                lcd.putstr("Status: ON      ")

            else:

                lcd.putstr("Status: OFF     ")

            lcd.move_to(0, 1)

            lcd.putstr("< Ubah >  OK:Kel") # Panduan tombol di baris kedua

           

        elif menu_index == 2: # --- TAMPILAN MENU 3: BATAS DAYA ---

            lcd.move_to(0, 0)

            if power_limit == 0:

                lcd.putstr("Limit: OFF      ")

            else:

                lcd.putstr(f"Limit: {power_limit} W      ")

            lcd.move_to(0, 1)

            lcd.putstr("<Kurang  Tambah>") # Panduan tombol di baris kedua

           

        elif menu_index == 3: # --- TAMPILAN MENU 4: STREAMING/LOGGER ---

            lcd.move_to(0, 0)

            if is_streaming:

                lcd.putstr("Status: LIVE OND") # Ada typo sedikit di 'OND', biasanya 'ON '

            else:

                lcd.putstr("Data Logger     ")

            lcd.move_to(0, 1)

            lcd.putstr("<Live    Simpan>") # Kiri untuk Live, Kanan untuk Simpan Log

           

        elif menu_index == 4: # --- TAMPILAN MENU 5: INFO SISTEM ---

            lcd.move_to(0, 0)

            lcd.putstr("Sistem v1.3     ") # Menampilkan versi sistem

            lcd.move_to(0, 1)

            lcd.putstr("Tekan OK: Keluar")

 

    # Memberikan sedikit waktu istirahat pada CPU Pico (100 milidetik per siklus)

    time.sleep(0.1)

c.

d.      Program lcd_api.py

import time

from lcd_api import LcdApi

 

# Mendeklarasikan class I2cLcd yang merupakan "anak" (turunan) dari class LcdApi

class I2cLcd(LcdApi):

    # ================= MAPPING PIN IC PCF8574 KE PIN LCD =================

    # Modul I2C di belakang LCD menggunakan IC PCF8574.

    # Konstanta di bawah ini digunakan untuk mengatur bit ke pin yang tepat menggunakan operasi Bitwise.

   

    MASK_RS = 0x01          # 0000 0001 -> Pin RS (Register Select). 0=Perintah, 1=Karakter/Teks

    MASK_RW = 0x02          # 0000 0010 -> Pin RW (Read/Write). Di modul ini selalu Write (biasanya tidak dipakai)

    MASK_E  = 0x04          # 0000 0100 -> Pin E (Enable). Kaki ini harus diberi pulsa (High ke Low) agar LCD membaca data

   

    SHIFT_BACKLIGHT = 3     # Lampu latar (Backlight) terhubung ke bit ke-3 (Posisi nilai 8)

    SHIFT_DATA      = 4     # Pin Data D4-D7 LCD terhubung ke bit ke-4 hingga ke-7 pada IC PCF8574

 

    def __init__(self, i2c, i2c_addr, num_lines, num_columns):

        """Fungsi Inisialisasi Hardware I2C ke LCD"""

        self.i2c = i2c              # Objek I2C dari Pico (misal I2C(0, sda=Pin(0), scl=Pin(1)))

        self.i2c_addr = i2c_addr    # Alamat I2C modul LCD (biasanya 0x27 atau 0x3F)

       

        # Kirim data kosong (0) untuk mereset kondisi pin pada IC PCF8574

        self.i2c.writeto(self.i2c_addr, bytearray([0]))

        time.sleep(0.020) # Tunggu 20ms agar tegangan stabil setelah power-on

       

        # --- PROSEDUR WAJIB INISIALISASI LCD MODE 4-BIT ---

        # Berdasarkan datasheet HD44780, untuk masuk ke mode 4-bit,

        # kita harus mengirim perintah khusus (0x3) sebanyak 3 kali berturut-turut.

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT)

        time.sleep(0.005) # Jeda 5ms

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT)

        time.sleep(0.001) # Jeda 1ms

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT)

        time.sleep(0.001)

       

        # Terakhir, kirim perintah (0x2) untuk mengunci LCD ke Mode 4-bit

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT >> 1)

        time.sleep(0.001)

       

        # Setelah hardware siap di mode 4-bit, panggil fungsi inisialisasi dari "induk" (LcdApi)

        super().__init__(num_lines, num_columns)

       

        # Pastikan pengaturan baris diterapkan (misal 2 baris)

        self.hal_write_command(self.LCD_FUNCTION | self.LCD_FUNCTION_2LINES)

 

    def hal_write_init_nibble(self, nibble):

        """Fungsi khusus untuk mengirim 4-bit data saat fase awal (booting) saja"""

        # Ambil 4-bit data, geser posisinya ke bit 4-7 (pin D4-D7)

        byte = ((nibble >> 4) & 0x0f) << self.SHIFT_DATA

       

        # Metode Pulsa Enable: Data dimasukkan, pin E dinyalakan (MASK_E)

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        # Pin E dimatikan. Saat transisi High ke Low inilah LCD membaca data tersebut

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

 

    def hal_backlight_on(self):

        """Menyalakan transistor lampu latar di modul I2C"""

        # Mengirim logika 1 yang digeser ke posisi bit lampu (bit ke-3)

        self.i2c.writeto(self.i2c_addr, bytearray([1 << self.SHIFT_BACKLIGHT]))

 

    def hal_backlight_off(self):

        """Mematikan transistor lampu latar"""

        # Kirim logika 0 ke seluruh pin

        self.i2c.writeto(self.i2c_addr, bytearray([0]))

 

    def hal_write_command(self, cmd):

        """Menerjemahkan perintah (Command) hex menjadi pulsa listrik I2C"""

        # Pin RS tetap 0 karena ini adalah Perintah (Command), bukan Teks (Data)

       

        # 1. PENGIRIMAN 4-BIT PERTAMA (HIGH NIBBLE)

        # Ambil bit lampu latar, lalu gabungkan dengan 4-bit atas dari data (cmd)

        byte = ((self.backlight << self.SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << self.SHIFT_DATA))

        # Berikan pulsa Enable (ON)

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        # Matikan pulsa Enable (OFF - LCD mengeksekusi)

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

       

        # 2. PENGIRIMAN 4-BIT KEDUA (LOW NIBBLE)

        # Ambil bit lampu latar, lalu gabungkan dengan 4-bit bawah dari data (cmd & 0x0f)

        byte = ((self.backlight << self.SHIFT_BACKLIGHT) | ((cmd & 0x0f) << self.SHIFT_DATA))

        # Berikan pulsa Enable (ON)

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        # Matikan pulsa Enable (OFF - LCD mengeksekusi)

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

       

        # Beberapa perintah berat seperti Clear Screen (0x01) atau Return Home (0x02) butuh waktu proses lebih lama di IC LCD

        if cmd <= 3:

            time.sleep(0.005) # Beri jeda ekstra 5ms agar IC LCD tidak hang

 

    def hal_write_data(self, data):

        """Menerjemahkan teks (Data) menjadi pulsa listrik I2C"""

        # Konsepnya sama persis dengan write_command di atas (dibagi 2 bagian High & Low).

        # PERBEDAANNYA HANYA SATU: Kita tambahkan MASK_RS (Register Select = 1)

        # Ini memberi tahu IC LCD bahwa sinyal yang masuk adalah huruf yang harus digambar di layar.

       

        # 1. Kirim 4-bit Pertama (High Nibble) + RS = 1

        byte = (self.MASK_RS | (self.backlight << self.SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << self.SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

       

        # 2. Kirim 4-bit Kedua (Low Nibble) + RS = 1

        byte = (self.MASK_RS | (self.backlight << self.SHIFT_BACKLIGHT) | ((data & 0x0f) << self.SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

      Program pico_i2c_lcd.py

import time

from lcd_api import LcdApi

 

# Mendeklarasikan class I2cLcd yang merupakan "anak" (turunan) dari class LcdApi

class I2cLcd(LcdApi):

    # ================= MAPPING PIN IC PCF8574 KE PIN LCD =================

    # Modul I2C di belakang LCD menggunakan IC PCF8574.

    # Konstanta di bawah ini digunakan untuk mengatur bit ke pin yang tepat menggunakan operasi Bitwise.

   

    MASK_RS = 0x01          # 0000 0001 -> Pin RS (Register Select). 0=Perintah, 1=Karakter/Teks

    MASK_RW = 0x02          # 0000 0010 -> Pin RW (Read/Write). Di modul ini selalu Write (biasanya tidak dipakai)

    MASK_E  = 0x04          # 0000 0100 -> Pin E (Enable). Kaki ini harus diberi pulsa (High ke Low) agar LCD membaca data

   

    SHIFT_BACKLIGHT = 3     # Lampu latar (Backlight) terhubung ke bit ke-3 (Posisi nilai 8)

    SHIFT_DATA      = 4     # Pin Data D4-D7 LCD terhubung ke bit ke-4 hingga ke-7 pada IC PCF8574

 

    def __init__(self, i2c, i2c_addr, num_lines, num_columns):

        """Fungsi Inisialisasi Hardware I2C ke LCD"""

        self.i2c = i2c              # Objek I2C dari Pico (misal I2C(0, sda=Pin(0), scl=Pin(1)))

        self.i2c_addr = i2c_addr    # Alamat I2C modul LCD (biasanya 0x27 atau 0x3F)

       

        # Kirim data kosong (0) untuk mereset kondisi pin pada IC PCF8574

        self.i2c.writeto(self.i2c_addr, bytearray([0]))

        time.sleep(0.020) # Tunggu 20ms agar tegangan stabil setelah power-on

       

        # --- PROSEDUR WAJIB INISIALISASI LCD MODE 4-BIT ---

        # Berdasarkan datasheet HD44780, untuk masuk ke mode 4-bit,

        # kita harus mengirim perintah khusus (0x3) sebanyak 3 kali berturut-turut.

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT)

        time.sleep(0.005) # Jeda 5ms

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT)

        time.sleep(0.001) # Jeda 1ms

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT)

        time.sleep(0.001)

       

        # Terakhir, kirim perintah (0x2) untuk mengunci LCD ke Mode 4-bit

        self.hal_write_init_nibble(self.LCD_FUNCTION_8BIT >> 1)

        time.sleep(0.001)

       

        # Setelah hardware siap di mode 4-bit, panggil fungsi inisialisasi dari "induk" (LcdApi)

        super().__init__(num_lines, num_columns)

       

        # Pastikan pengaturan baris diterapkan (misal 2 baris)

        self.hal_write_command(self.LCD_FUNCTION | self.LCD_FUNCTION_2LINES)

 

    def hal_write_init_nibble(self, nibble):

        """Fungsi khusus untuk mengirim 4-bit data saat fase awal (booting) saja"""

        # Ambil 4-bit data, geser posisinya ke bit 4-7 (pin D4-D7)

        byte = ((nibble >> 4) & 0x0f) << self.SHIFT_DATA

       

        # Metode Pulsa Enable: Data dimasukkan, pin E dinyalakan (MASK_E)

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        # Pin E dimatikan. Saat transisi High ke Low inilah LCD membaca data tersebut

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

 

    def hal_backlight_on(self):

        """Menyalakan transistor lampu latar di modul I2C"""

        # Mengirim logika 1 yang digeser ke posisi bit lampu (bit ke-3)

        self.i2c.writeto(self.i2c_addr, bytearray([1 << self.SHIFT_BACKLIGHT]))

 

    def hal_backlight_off(self):

        """Mematikan transistor lampu latar"""

        # Kirim logika 0 ke seluruh pin

        self.i2c.writeto(self.i2c_addr, bytearray([0]))

 

    def hal_write_command(self, cmd):

        """Menerjemahkan perintah (Command) hex menjadi pulsa listrik I2C"""

        # Pin RS tetap 0 karena ini adalah Perintah (Command), bukan Teks (Data)

       

        # 1. PENGIRIMAN 4-BIT PERTAMA (HIGH NIBBLE)

        # Ambil bit lampu latar, lalu gabungkan dengan 4-bit atas dari data (cmd)

        byte = ((self.backlight << self.SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << self.SHIFT_DATA))

        # Berikan pulsa Enable (ON)

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        # Matikan pulsa Enable (OFF - LCD mengeksekusi)

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

       

        # 2. PENGIRIMAN 4-BIT KEDUA (LOW NIBBLE)

        # Ambil bit lampu latar, lalu gabungkan dengan 4-bit bawah dari data (cmd & 0x0f)

        byte = ((self.backlight << self.SHIFT_BACKLIGHT) | ((cmd & 0x0f) << self.SHIFT_DATA))

        # Berikan pulsa Enable (ON)

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        # Matikan pulsa Enable (OFF - LCD mengeksekusi)

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

       

        # Beberapa perintah berat seperti Clear Screen (0x01) atau Return Home (0x02) butuh waktu proses lebih lama di IC LCD

        if cmd <= 3:

            time.sleep(0.005) # Beri jeda ekstra 5ms agar IC LCD tidak hang

 

    def hal_write_data(self, data):

        """Menerjemahkan teks (Data) menjadi pulsa listrik I2C"""

        # Konsepnya sama persis dengan write_command di atas (dibagi 2 bagian High & Low).

        # PERBEDAANNYA HANYA SATU: Kita tambahkan MASK_RS (Register Select = 1)

        # Ini memberi tahu IC LCD bahwa sinyal yang masuk adalah huruf yang harus digambar di layar.

       

        # 1. Kirim 4-bit Pertama (High Nibble) + RS = 1

        byte = (self.MASK_RS | (self.backlight << self.SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << self.SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

       

        # 2. Kirim 4-bit Kedua (Low Nibble) + RS = 1

        byte = (self.MASK_RS | (self.backlight << self.SHIFT_BACKLIGHT) | ((data & 0x0f) << self.SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | self.MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

IV. HASIL DAN PEMBAHASAN

A. Gambar Alat

Gambar 9. Gambar Alat

B. Cara kerja

Sistem Stop Kontak Pintar++ bekerja secara otomatis dan terintegrasi melalui siklus pengambilan data, pengolahan data, hingga eksekusi proteksi serta visualisasi informasi. Siklus kerja ini dimulai ketika beban listrik (seperti lampu) dihubungkan ke stop kontak pintar yang tersambung dengan sumber listrik utama PLN bertegangan AC 220V 50 Hz. Pada titik distribusi beban ini, sensor PZEM-004T langsung bekerja membaca parameter kelistrikan secara waktu nyata (real-time). Sensor ini secara simultan mengukur besaran fisis berupa tegangan, arus, daya aktif (Watt), serta akumulasi energi (Wh) yang diserap oleh beban.

Setelah parameter kelistrikan terbaca, sensor PZEM-004T mentransmisikan data pengukuran tersebut ke mikrokontroler Raspberry Pi Pico melalui jalur komunikasi serial UART. Mengingat adanya perbedaan tegangan kerja, sinyal logika komunikasi ini melewati komponen logic shifter terlebih dahulu untuk menyesuaikan level tegangan agar aman bagi pin GPIO mikrokontroler. Di dalam Raspberry Pi Pico, data mentah tersebut kemudian diolah secara komputasi. Mikrokontroler tidak hanya bertugas menghitung data, tetapi juga memproses input dari tiga tombol fisik (push button) yang ditekan oleh pengguna. Tombol-tombol ini berfungsi untuk menavigasi menu alat—seperti menu Parameter, Kontrol Relay, Batas Daya, dan Live Stream—yang disertai dengan umpan balik (feedback) suara dari komponen buzzer.

Hasil pengolahan data dari mikrokontroler kemudian disalurkan ke dua jalur keluaran utama. Jalur pertama adalah tampilan lokal pada layar LCD 16x2 via komunikasi I2C yang menampilkan informasi numerik sesuai dengan menu navigasi yang dipilih pengguna. Jalur kedua adalah pengiriman aliran data (live streaming) menuju gawai atau laptop melalui koneksi kabel USB. Di sisi laptop, data serial ini ditangkap oleh aplikasi dashboard interaktif untuk divisualisasikan dalam bentuk grafik pemantauan secara langsung dan dapat diekspor menjadi laporan tertulis dalam format Excel.

Bersamaan dengan proses pemantauan tersebut, sistem kendali proteksi aktif berjalan di latar belakang. Raspberry Pi Pico secara terus-menerus membandingkan nilai daya aktif (Watt) aktual hasil bacaan sensor dengan batas daya maksimum (threshold) yang telah diatur oleh pengguna. Jika daya aktual terdeteksi melebihi batas aman tersebut, mikrokontroler akan langsung memicu algoritma proteksi pintar (Over-Power Protection) dan mengirimkan sinyal digital untuk memutus modul relay. Putusnya sakelar relay ini secara otomatis langsung menghentikan aliran listrik menuju beban guna mencegah terjadinya panas berlebih (overheating) atau bahaya korsleting listrik. Pengoperasian relay ini juga dapat dikontrol secara manual oleh pengguna melalui menu yang tersedia pada alat.

 

 

 

C. Hasil

 

Gambar 10. Tampilan Saat Alat Bekerja


Gambar 11. Tampilan Monitoring

Berdasarkan hasil implementasi dan pengujian yang telah dilakukan, sistem "Stop Kontak Pintar++" berbasis Raspberry Pi Pico dan sensor PZEM-004T ini berhasil mendeteksi dan memonitor berbagai parameter kelistrikan seperti tegangan, arus, daya, energi, frekuensi, dan power factor secara waktu nyata (real-time) dengan baik. Integrasi perangkat keras menunjukkan bahwa seluruh komponen utama, termasuk modul relay 5V, tiga tombol fisik sebagai navigator menu, serta indikator buzzer, dapat beroperasi secara sinkron di bawah kendali mikrokontroler. Informasi data kelistrikan lokal dapat ditampilkan secara interaktif pada layar LCD . Selain itu, fitur live streaming data via USB ke aplikasi dashboard di laptop berjalan dengan stabil, memungkinkan visualisasi grafik pemakaian listrik secara langsung serta pencetakan laporan data ke format Excel (CSV) secara akurat. Pengujian sistem proteksi pintar (Over-Power Protection) juga menunjukkan respons yang cepat dan adaptif, di mana modul relay berhasil memutus aliran listrik secara otomatis ketika beban terdeteksi melebihi batas daya yang telah diatur oleh pengguna. Secara keseluruhan, alat ini terbukti berfungsi dengan baik dan memiliki potensi besar untuk diterapkan pada sistem manajemen energi maupun ekosistem rumah pintar (smart home).


V. KESIMPULAN

Berdasarkan hasil perancangan, implementasi, dan pengujian yang telah dilakukan, dapat disimpulkan bahwa proyek sistem Stop Kontak Pintar++ berbasis mikrokontroler Raspberry Pi Pico dan sensor PZEM-004T telah berhasil dibuat dan berfungsi secara optimal. Sistem ini terbukti mampu memonitor berbagai parameter kelistrikan vital seperti tegangan, arus, daya, dan energi secara waktu nyata (real-time), serta menampilkannya secara interaktif pada layar LCD 16x2 lokal. Selain itu, integrasi dengan aplikasi dashboard di laptop melalui koneksi USB berjalan dengan baik, sehingga pengguna dapat memantau grafik pemakaian langsung serta mengekspor data laporan ke dalam format Excel dengan mudah. Fitur keamanan aktif berupa sistem proteksi pintar (Over-Power Protection) juga menunjukkan performa yang responsif, di mana modul relay secara otomatis memutus aliran listrik dengan baik saat mendeteksi adanya beban berlebih yang melewati batas aman yang diatur pengguna. Secara keseluruhan, alat ini tidak hanya ramah pengguna dan aplikatif, melainkan juga memiliki potensi besar untuk dikembangkan lebih lanjut dalam ekosistem rumah pintar (smart home), manajemen energi, serta peningkatan efisiensi penggunaan listrik di masa depan.


VI. REFERENSI

1.       Raspberry Pi Ltd. (2020). Raspberry Pi Pico Datasheet: An RP2040-based microcontroller board. Cambridge: Raspberry Pi Press.

2.       Ningbo Peacefair Electronic Technology Co., Ltd. (2018). PZEM-004T V3.0 AC Electronic Module User Manual. Ningbo: Peacefair.

3.       Songle Relay Co., Ltd. (2016). SRD Series Relay Datasheet (SRD-05VDC-SL-C). Ningbo: Songle.

4.       Budiharto, W. (2020). Rancang Bangun Sistem Elektronika dan IoT Berbasis Mikrokontroler. Jakarta: Penerbit Andi.

5.       Ibrahim, D. (2015). Raspberry Pi Embedded Projects Hotshot. Birmingham: Packt Publishing.

6.       Setiawan, A., & Ramadhani, S. (2022). Sistem Monitoring Konsumsi Energi Listrik Berbasis IoT Menggunakan Sensor PZEM-004T. Jurnal Teknologi dan Sistem Komputer, 10(2), 115-122.

7.       Badan Standardisasi Nasional. (2011). Persyaratan Umum Instalasi Listrik 2011 (PUIL 2011). SNI 0225:2011. Jakarta: BSN.

8.       International Electrotechnical Commission. (2020). IEC 62053-21: Electricity metering equipment - Particular requirements - Part 21: Static meters for AC active energy (classes 0,5, 1 and 2). Geneva: IEC.

9.       Python Software Foundation. (2025). MicroPython Documentation for Raspberry Pi Pico. Diakses dari https://docs.micropython.org/

10.    EasyEDA Team. (2026). EasyEDA User Manual: Schematic and PCB Design Schematic. Diakses dari https://docs.easyeda.com/

 

Penulis atas nama Aldino Sultansyah dilahirkan di Semarang, 2 Desember 2005. Penulis telah menempuh pendidikan formal di SD Negeri Tambakaji 04, SMP Negeri 16 Semarang, dan SMK Penerbangan Semarang. Tahun 2023 penulis menyelesaikan pendidikannya di SMK. Pada tahun 2023 penulis mengikuti seleksi mahasiswa baru Sarjana Terapan (D4) dan diterima menjadi mahasiswa baru Sarjana Terapan (D4) dikampus Politeknik Negeri Semarang (Polines) dengan Program Studi D4 Teknologi Rekayasa Elektronika, Jurusan Teknik Elektro. Penulis terdaftar dengan NIM. 4.34.23.0.03. Apabila ada kritik, saran dan pertanyaan mengenai penelitian ini, bisa via email:  aldino.43423003@mhs.polines.ac.id

Penulis atas nama Frisca Syaharani dilahirkan di Kab. Semarang, 29 September 2005. Penulis telah menempuh pendidikan formal di SD Negeri 04 Banyubiru, SMP Negeri 01 Banyubiru, dan SMK Negeri 2 Salatiga. Tahun 2023 penulis menyelesaikan pendidikannya di SMK. Pada tahun 2023 penulis mengikuti seleksi mahasiswa baru Sarjana Terapan (D4) dan diterima menjadi mahasiswa baru Sarjana Terapan (D4) dikampus Politeknik Negeri Semarang (Polines) dengan Program Studi D4 Teknologi Rekayasa Elektronika, Jurusan Teknik Elektro. Penulis terdaftar dengan NIM. 4.34.23.0.09. Apabila ada kritik, saran dan pertanyaan mengenai penelitian ini, bisa via email:  frisca.43423009@mhs.polines.ac.id

Penulis atas nama Muhammad Satrio Adhy dilahirkan di Semarang, 29 Januari 2005 . Penulis telah menempuh pendidikan formal di SD Negeri Purwoyoso 03, SMP Negeri 18 Semarang, dan SMA Islam Sultan Agung 1 Semarang. Tahun 2023 penulis menyelesaikan pendidikannya di SMA. Pada tahun 2023 penulis mengikuti seleksi mahasiswa baru Sarjana Terapan (D4) dan diterima menjadi mahasiswa baru Sarjana Terapan (D4) dikampus Politeknik Negeri Semarang (Polines) dengan Program Studi D4- Teknologi Rekayasa Elektronika, Jurusan Teknik Elektro. Penulis terdaftar dengan NIM 4.34.23.0.15. Apabila ada kritik, saran dan pertanyaan mengenai penelitian ini, bisa via email:    satrio.43423015@mhs.polines.ac.id  

Penulis atas nama Salsabila Chairunisa dilahirkan di Salatiga, 15 Mei 2004. Penulis telah menempuh pendidikan formal di SDIT Darul Falah Semarang, SMP Negeri 20 Semarang, dan SMK Negeri 3 Salatiga. Tahun 2023 penulis telah menyelesaikan pendidikannya di SMK. Pada tahun 2023 penulis mengikuti seleksi mahasiswa baru Sarjana Terapan(D4) dan diterima menjadi mahasiswa baru Sarjana Terapan(D4) di kampus Politeknik Negeri Semarang(Polines) dengan program studi D4 Teknologi Rekayasa Elektronika, Jurusan Teknik Elektro. Penulis terdaftar dengan NIM 4.34.23.0.22 Apabila ada kritik, saran dan pertanyaan mengenai penelitian ini, bisa via email: salsabila.43423022@mhs.polines.ac.id

PRESENTASI

https://canva.link/6bmp7ocijhz0i2j


 


Komentar

Postingan populer dari blog ini

SISTEM KONVEYOR OTOMATIS DENGAN SENSOR INFRARED DAN KONTROL MANUAL

Pompa Air Otomatis Berbasis ATMega8535

SISTEM PEMANTAUAN SUHU DAN KELEMBABAN PADA SUATU RUANGAN MENGGUNAKAN SENSOR DHT22 BERBASIS MIKROKONTROLLER ARDUINO UNO ATMEGA328P