# main.py — Smart Parking Monitor
# MicroPython ESP32 | Wokwi Free (tanpa library eksternal)
import uasyncio as asyncio
import network
import time
from config import WIFI_SSID, WIFI_PASS
from parking import ParkingMonitor
from firebase import FirebaseClient
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if wlan.isconnected():
return True
print("Connecting WiFi...")
wlan.connect(WIFI_SSID, WIFI_PASS)
for _ in range(20):
if wlan.isconnected():
print("WiFi OK:", wlan.ifconfig()[0])
return True
time.sleep(1)
print("WiFi gagal — mode offline")
return False
async def task_sensor(monitor):
while True:
monitor.read_all_sensors()
monitor.update_led()
monitor.update_lcd()
await asyncio.sleep_ms(300)
async def task_buttons(monitor):
while True:
monitor.read_buttons()
await asyncio.sleep_ms(50)
async def task_firebase(monitor, fb):
while True:
await asyncio.sleep(5)
data = monitor.get_state_dict()
ok = fb.patch("parking", data)
print("Firebase:", "OK" if ok else "GAGAL")
if ok:
fb.post("history", {
"occupied" : data["occupied"],
"freeSlots": data["freeSlots"],
"ts" : data["uptime_s"],
})
async def main():
wifi_ok = connect_wifi()
monitor = ParkingMonitor()
monitor.lcd_clear()
monitor.lcd_print("Smart Parking", row=0)
monitor.lcd_print("MicroPython IoT", row=1)
time.sleep(2)
fb = FirebaseClient() if wifi_ok else None
tasks = [
asyncio.create_task(task_sensor(monitor)),
asyncio.create_task(task_buttons(monitor)),
]
if fb:
tasks.append(asyncio.create_task(task_firebase(monitor, fb)))
await asyncio.gather(*tasks)
asyncio.run(main())
{
"version": 1,
"author": "Smart Parking MicroPython",
"editor": "wokwi",
"parts": [
{
"type": "board-esp32-devkit-c-v4",
"id": "esp",
"top": 80,
"left": 120,
"attrs": { "env": "micropython-20231227-v1.22.0" }
},
{
"type": "wokwi-hc-sr04",
"id": "sr1",
"top": -60,
"left": 420,
"attrs": { "distance": "25" }
},
{
"type": "wokwi-hc-sr04",
"id": "sr2",
"top": 60,
"left": 420,
"attrs": { "distance": "25" }
},
{
"type": "wokwi-hc-sr04",
"id": "sr3",
"top": 180,
"left": 420,
"attrs": { "distance": "25" }
},
{ "type": "wokwi-lcd1602", "id": "lcd", "top": 339.43, "left": 198.4, "attrs": {} },
{ "type": "wokwi-led", "id": "ledR", "top": -60, "left": -80, "attrs": { "color": "red" } },
{ "type": "wokwi-led", "id": "ledY", "top": 30, "left": -80, "attrs": { "color": "yellow" } },
{ "type": "wokwi-led", "id": "ledG", "top": 120, "left": -80, "attrs": { "color": "green" } },
{
"type": "wokwi-pushbutton",
"id": "btn1",
"top": 460,
"left": 80,
"attrs": { "color": "green" }
},
{
"type": "wokwi-pushbutton",
"id": "btn2",
"top": 460,
"left": 200,
"attrs": { "color": "yellow" }
},
{
"type": "wokwi-pushbutton",
"id": "btn3",
"top": 460,
"left": 320,
"attrs": { "color": "red" }
},
{ "type": "wokwi-resistor", "id": "r1", "top": -40, "left": -40, "attrs": { "value": "220" } },
{ "type": "wokwi-resistor", "id": "r2", "top": 50, "left": -40, "attrs": { "value": "220" } },
{ "type": "wokwi-resistor", "id": "r3", "top": 140, "left": -40, "attrs": { "value": "220" } }
],
"connections": [
[ "esp:TX", "$serialMonitor:RX", "", [] ],
[ "esp:RX", "$serialMonitor:TX", "", [] ],
[ "esp:5", "sr1:TRIG", "blue", [] ],
[ "esp:18", "sr1:ECHO", "green", [] ],
[ "esp:19", "sr2:TRIG", "blue", [] ],
[ "esp:21", "sr2:ECHO", "green", [] ],
[ "esp:22", "sr3:TRIG", "blue", [] ],
[ "esp:23", "sr3:ECHO", "green", [] ],
[ "sr1:VCC", "esp:3V3", "red", [] ],
[ "sr1:GND", "esp:GND", "black", [] ],
[ "sr2:VCC", "esp:3V3", "red", [] ],
[ "sr2:GND", "esp:GND", "black", [] ],
[ "sr3:VCC", "esp:3V3", "red", [] ],
[ "sr3:GND", "esp:GND", "black", [] ],
[ "esp:25", "r1:1", "orange", [] ],
[ "r1:2", "ledR:A", "orange", [] ],
[ "ledR:K", "esp:GND", "black", [] ],
[ "esp:26", "r2:1", "yellow", [] ],
[ "r2:2", "ledY:A", "yellow", [] ],
[ "ledY:K", "esp:GND", "black", [] ],
[ "esp:27", "r3:1", "lime", [] ],
[ "r3:2", "ledG:A", "lime", [] ],
[ "ledG:K", "esp:GND", "black", [] ],
[ "esp:13", "lcd:RS", "purple", [] ],
[ "esp:12", "lcd:E", "purple", [] ],
[ "esp:14", "lcd:D4", "blue", [] ],
[ "esp:2", "lcd:D5", "blue", [] ],
[ "esp:4", "lcd:D6", "blue", [] ],
[ "esp:15", "lcd:D7", "blue", [] ],
[ "lcd:VSS", "esp:GND", "black", [] ],
[ "lcd:VDD", "esp:3V3", "red", [] ],
[ "lcd:V0", "esp:GND", "black", [] ],
[ "lcd:A", "esp:3V3", "red", [] ],
[ "lcd:K", "esp:GND", "black", [] ],
[ "lcd:RW", "esp:GND", "black", [] ],
[ "esp:32", "btn1:1.l", "white", [] ],
[ "btn1:2.l", "esp:GND", "black", [] ],
[ "esp:33", "btn2:1.l", "white", [] ],
[ "btn2:2.l", "esp:GND", "black", [] ],
[ "esp:34", "btn3:1.l", "white", [] ],
[ "btn3:2.l", "esp:GND", "black", [] ]
],
"dependencies": {}
}
# parking.py — ParkingMonitor
# LCD 16x2 4-bit parallel, tanpa library eksternal
# Fix: tidak pakai ljust (tidak tersedia di MicroPython v1.22.0)
from machine import Pin, time_pulse_us
import time
from config import (
TRIG_PINS, ECHO_PINS, BTN_PINS,
LED_RED, LED_YLW, LED_GRN,
LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7,
THRESHOLD_CM,
)
SOUND_SPEED = 0.0343
MAX_ECHO_US = 30_000
DEBOUNCE_MS = 200
# ── Helper: pad string ke 16 karakter ────────────────
def pad16(text):
t = str(text)[:16]
return t + " " * (16 - len(t))
# ── Driver LCD HD44780 4-bit (hanya pakai machine.Pin) ─
class GpioLcd:
def __init__(self, rs, en, d4, d5, d6, d7):
self.rs = Pin(rs, Pin.OUT)
self.en = Pin(en, Pin.OUT)
self.d = [Pin(p, Pin.OUT) for p in (d4, d5, d6, d7)]
self._init_lcd()
def _pulse(self):
self.en.on()
time.sleep_us(1)
self.en.off()
time.sleep_us(50)
def _write4(self, val):
for i, pin in enumerate(self.d):
pin.value((val >> i) & 1)
self._pulse()
def _send(self, val, mode):
self.rs.value(mode)
self._write4(val >> 4)
self._write4(val & 0x0F)
def _cmd(self, c):
self._send(c, 0)
time.sleep_ms(2)
def _init_lcd(self):
time.sleep_ms(50)
self.rs.off()
self.en.off()
for _ in range(3):
self._write4(0x03)
time.sleep_ms(5)
self._write4(0x02)
self._cmd(0x28)
self._cmd(0x0C)
self._cmd(0x06)
self._cmd(0x01)
time.sleep_ms(5)
def clear(self):
self._cmd(0x01)
time.sleep_ms(2)
def move_to(self, col, row):
addr = col + (0x40 if row else 0x00)
self._cmd(0x80 | addr)
def putstr(self, text):
for ch in text:
self._send(ord(ch), 1)
# ── ParkingMonitor ────────────────────────────────────
class ParkingMonitor:
def __init__(self):
self.trig = [Pin(p, Pin.OUT) for p in TRIG_PINS]
self.echo = [Pin(p, Pin.IN) for p in ECHO_PINS]
self.led_r = Pin(LED_RED, Pin.OUT)
self.led_y = Pin(LED_YLW, Pin.OUT)
self.led_g = Pin(LED_GRN, Pin.OUT)
self.btns = [Pin(p, Pin.IN, Pin.PULL_DOWN) for p in BTN_PINS]
self.btn_last = [False] * 3
self.btn_time = [0] * 3
self.lcd = GpioLcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7)
self.occupied = [False] * 3
self.distances = [999.0] * 3
self.total_vehicles = 0
self.start_ms = time.ticks_ms()
def measure_distance(self, idx):
trig = self.trig[idx]
echo = self.echo[idx]
trig.off()
time.sleep_us(2)
trig.on()
time.sleep_us(10)
trig.off()
dur = time_pulse_us(echo, 1, MAX_ECHO_US)
return 999.0 if dur < 0 else (dur * SOUND_SPEED) / 2.0
def read_all_sensors(self):
for i in range(3):
dist = self.measure_distance(i)
self.distances[i] = dist
prev = self.occupied[i]
self.occupied[i] = dist < THRESHOLD_CM
if self.occupied[i] and not prev:
self.total_vehicles += 1
print("Slot", i + 1, "TERISI —", round(dist, 1), "cm")
def read_buttons(self):
now = time.ticks_ms()
for i in range(3):
cur = bool(self.btns[i].value())
if cur != self.btn_last[i] and \
time.ticks_diff(now, self.btn_time[i]) > DEBOUNCE_MS:
self.btn_time[i] = now
self.btn_last[i] = cur
if cur:
self.occupied[i] = not self.occupied[i]
if self.occupied[i]:
self.total_vehicles += 1
status = "TERISI" if self.occupied[i] else "KOSONG"
print("BTN Slot", i + 1, ":", status)
def update_led(self):
free = self.free_slots()
self.led_r.value(1 if free == 0 else 0)
self.led_y.value(1 if free == 1 else 0)
self.led_g.value(1 if free >= 2 else 0)
def lcd_clear(self):
self.lcd.clear()
def lcd_print(self, text, row=0, col=0):
self.lcd.move_to(col, row)
self.lcd.putstr(pad16(text))
def update_lcd(self):
free = self.free_slots()
# Baris 1: status tiap slot → "1[X] 2[_] 3[X]"
line1 = ""
for i in range(3):
line1 = line1 + str(i + 1)
line1 = line1 + ("[X]" if self.occupied[i] else "[_]")
if i < 2:
line1 = line1 + " "
self.lcd_print(line1, row=0)
# Baris 2: ringkasan
if free == 0:
line2 = "PENUH! Tot:" + str(self.total_vehicles)
else:
line2 = "Kosong:" + str(free) + " Tot:" + str(self.total_vehicles)
self.lcd_print(line2, row=1)
def free_slots(self):
count = 0
for o in self.occupied:
if not o:
count += 1
return count
def get_state_dict(self):
uptime = time.ticks_diff(time.ticks_ms(), self.start_ms) // 1000
return {
"slot1" : self.occupied[0],
"slot2" : self.occupied[1],
"slot3" : self.occupied[2],
"occupied" : 3 - self.free_slots(),
"freeSlots": self.free_slots(),
"totalCars": self.total_vehicles,
"dist1_cm" : round(self.distances[0], 1),
"dist2_cm" : round(self.distances[1], 1),
"dist3_cm" : round(self.distances[2], 1),
"uptime_s" : uptime,
}
# firebase.py
import urequests
import ujson
from config import FIREBASE_URL, FIREBASE_SECRET
class FirebaseClient:
def __init__(self):
self.base = FIREBASE_URL.rstrip("/")
self.secret = FIREBASE_SECRET
self.headers = {"Content-Type": "application/json"}
def _url(self, path):
return f"{self.base}/{path}.json?auth={self.secret}"
def patch(self, path, data) -> bool:
try:
r = urequests.patch(self._url(path), data=ujson.dumps(data), headers=self.headers)
ok = r.status_code == 200
r.close(); return ok
except Exception as e:
print("PATCH err:", e); return False
def post(self, path, data) -> str:
try:
r = urequests.post(self._url(path), data=ujson.dumps(data), headers=self.headers)
key = ujson.loads(r.text).get("name", "")
r.close(); return key
except Exception as e:
print("POST err:", e); return ""
def get(self, path) -> dict:
try:
r = urequests.get(self._url(path))
data = ujson.loads(r.text)
r.close(); return data or {}
except Exception as e:
print("GET err:", e); return {}
# config.py
WIFI_SSID = "Wokwi-GUEST"
WIFI_PASS = ""
FIREBASE_URL = "masukkan URL"
FIREBASE_SECRET = "setting dari service account"
TRIG_PINS = [5, 19, 22]
ECHO_PINS = [18, 21, 23]
BTN_PINS = [32, 33, 34]
LED_RED = 25
LED_YLW = 26
LED_GRN = 27
LCD_RS = 13
LCD_EN = 12
LCD_D4 = 14
LCD_D5 = 2
LCD_D6 = 4
LCD_D7 = 15
THRESHOLD_CM = 10