import machine
import time
import neopixel
# ==============================================================================
# 1. BAGIAN LIBRARY LCD
# ==============================================================================
class LcdApi:
LCD_CLR, LCD_HOME, LCD_ENTRY_MODE, LCD_ENTRY_INC = 0x01, 0x02, 0x04, 0x02
LCD_ON_CTRL, LCD_ON_DISPLAY = 0x08, 0x04
LCD_FUNCTION, LCD_FUNCTION_8BIT, LCD_FUNCTION_2LINES = 0x20, 0x10, 0x08
LCD_DDRAM = 0x80
def __init__(self, num_lines, num_columns):
self.num_lines, self.num_columns = num_lines, num_columns
self.cursor_x, self.cursor_y, self.implied_newline = 0, 0, False
self.backlight = True
self.display_off()
self.backlight_on()
self.clear()
self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)
self.display_on()
def clear(self):
self.hal_write_command(self.LCD_CLR)
self.hal_write_command(self.LCD_HOME)
self.cursor_x, self.cursor_y = 0, 0
def display_on(self): self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
def display_off(self): self.hal_write_command(self.LCD_ON_CTRL)
def backlight_on(self): self.backlight = True; self.hal_backlight_on()
def backlight_off(self): self.backlight = False; self.hal_backlight_off()
def move_to(self, cursor_x, cursor_y):
self.cursor_x, self.cursor_y = cursor_x, cursor_y
addr = cursor_x & 0x3f
if cursor_y == 1: addr += 0x40
elif cursor_y == 2: addr += 0x14
elif cursor_y == 3: addr += 0x54
self.hal_write_command(self.LCD_DDRAM | addr)
def putchar(self, char):
if char == '\n':
if not self.implied_newline:
self.cursor_x = 0
self.cursor_y = (self.cursor_y + 1) % self.num_lines
self.move_to(self.cursor_x, self.cursor_y)
else:
self.hal_write_data(ord(char))
self.cursor_x += 1
if self.cursor_x >= self.num_columns:
self.cursor_x, self.cursor_y = 0, (self.cursor_y + 1) % self.num_lines
self.move_to(self.cursor_x, self.cursor_y)
self.implied_newline = True
else:
self.implied_newline = False
def putstr(self, string):
for char in string: self.putchar(char)
MASK_RS, MASK_RW, MASK_E = 0x01, 0x02, 0x04
SHIFT_BACKLIGHT, SHIFT_DATA = 3, 4
class I2cLcd(LcdApi):
def __init__(self, i2c, i2c_addr, num_lines, num_columns):
self.i2c, self.i2c_addr, self.backlight = i2c, i2c_addr, True
time.sleep(0.05)
self.i2c.writeto(self.i2c_addr, bytearray([0]))
time.sleep(0.02)
for _ in range(3):
self.hal_write_init_nibble(self.LCD_FUNCTION | self.LCD_FUNCTION_8BIT)
time.sleep(0.005)
self.hal_write_init_nibble(self.LCD_FUNCTION)
time.sleep(0.005)
self.hal_write_command(self.LCD_FUNCTION | self.LCD_FUNCTION_2LINES)
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
self.hal_write_command(self.LCD_CLR)
self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)
LcdApi.__init__(self, num_lines, num_columns)
def hal_write_init_nibble(self, nibble):
byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E, byte]))
def hal_backlight_on(self): self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT]))
def hal_backlight_off(self): self.i2c.writeto(self.i2c_addr, bytearray([0]))
def hal_write_command(self, cmd):
byte_high = (self.backlight << SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << SHIFT_DATA)
byte_low = (self.backlight << SHIFT_BACKLIGHT) | ((cmd & 0x0f) << SHIFT_DATA)
self.i2c.writeto(self.i2c_addr, bytearray([byte_high | MASK_E, byte_high]))
self.i2c.writeto(self.i2c_addr, bytearray([byte_low | MASK_E, byte_low]))
if cmd <= 3: time.sleep(0.005)
def hal_write_data(self, data):
byte_high = MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << SHIFT_DATA)
byte_low = MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | ((data & 0x0f) << SHIFT_DATA)
self.i2c.writeto(self.i2c_addr, bytearray([byte_high | MASK_E, byte_high]))
self.i2c.writeto(self.i2c_addr, bytearray([byte_low | MASK_E, byte_low]))
# ==============================================================================
# 2. KONFIGURASI HARDWARE UTAMA
# ==============================================================================
# 6 LED Biasa (karena di diagram merah semua, kita nyalakan secara logis kiri-kanan)
led_pins = [machine.Pin(pin, machine.Pin.OUT) for pin in [21, 20, 19, 18, 17, 16]]
NUM_LEDS = 16
np = neopixel.NeoPixel(machine.Pin(5), NUM_LEDS)
buzzer = machine.PWM(machine.Pin(1))
pot1 = machine.ADC(26) # Kecepatan
pot2 = machine.ADC(27) # Volume
ir_pin = machine.Pin(2, machine.Pin.IN)
current_mode = 1
i2c = machine.SoftI2C(sda=machine.Pin(14), scl=machine.Pin(13), freq=400000)
lcd = I2cLcd(i2c, 0x27, 4, 20)
# ==============================================================================
# 3. LOGIKA IR REMOTE (5 MODE)
# ==============================================================================
mode_names = {
1: "Ambulan",
2: "Polisi",
3: "Pemadam",
4: "Pengawal VIP",
5: "Patroli Sunyi"
}
def ir_callback(cmd):
global current_mode, last_sync_time, siren_high, anim_counter
mapping = {48: 1, 24: 2, 122: 3, 16: 4, 56: 5, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
if cmd in mapping:
current_mode = mapping[cmd]
print(f"\n>> Pindah ke MODE {current_mode} ({mode_names[current_mode]})")
anim_counter = 0
siren_high = True
for i in range(NUM_LEDS): np[i] = (0, 0, 0)
np.write()
for pin in led_pins: pin.value(0)
buzzer.duty_u16(0)
last_sync_time = time.ticks_ms()
lcd.move_to(0, 1)
lcd.putstr(f"Mode: {mode_names[current_mode]:<14}")
class IRReceiver:
def __init__(self, pin, callback):
self.pin, self.cb = pin, callback
self.last_tick, self.state, self.val, self.bits = time.ticks_us(), 0, 0, 0
self.pin.irq(trigger=machine.Pin.IRQ_FALLING | machine.Pin.IRQ_RISING, handler=self.rx_isr)
def rx_isr(self, p):
t = time.ticks_us()
dt = time.ticks_diff(t, self.last_tick)
self.last_tick = t
v = p.value()
if self.state == 0:
if v == 0: self.state = 1
elif self.state == 1:
if v == 1 and 8000 < dt < 10000: self.state = 2
else: self.state = 0
elif self.state == 2:
if v == 0 and 4000 < dt < 5000: self.state, self.val, self.bits = 3, 0, 0
else: self.state = 0
elif self.state == 3:
if v == 1: pass
elif v == 0:
if 1500 < dt < 2000:
self.val |= (1 << self.bits)
self.bits += 1
elif 400 < dt < 700: self.bits += 1
else: self.state = 0; return
if self.bits == 32:
cmd = (self.val >> 16) & 0xFF
self.cb(cmd)
self.state = 0
ir = IRReceiver(ir_pin, ir_callback)
# ==============================================================================
# 4. STARTUP SEQUENCE
# ==============================================================================
lcd.clear()
lcd.putstr("SISTEM MULTI-SIRINE\nMemulai sistem...\nMohon tunggu...")
time.sleep(1)
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr("=== PANEL SIRINE ===")
lcd.move_to(0, 1)
lcd.putstr(f"Mode: {mode_names[current_mode]}")
siren_high = True
last_sync_time, last_lcd_time = time.ticks_ms(), time.ticks_ms()
last_speed_str, last_vol_str = "", ""
anim_counter = 0
RED, WHITE, BLUE = (255, 0, 0), (255, 255, 255), (0, 0, 255)
ORANGE, WARM_RED, YELLOW = (255, 100, 0), (255, 30, 0), (255, 200, 0)
OFF = (0, 0, 0)
# ==============================================================================
# 5. LOOP UTAMA
# ==============================================================================
while True:
current_time = time.ticks_ms()
# --- Sensor ---
val1 = pot1.read_u16()
val2 = pot2.read_u16()
if val1 < 22000:
speed_delay, speed_txt = 100, "Lambat"
elif val1 < 44000:
speed_delay, speed_txt = 60, "Sedang"
else:
speed_delay, speed_txt = 30, "Cepat "
volume = int((val2 / 65535.0) * 32768)
vol_pct = int((val2 / 65535.0) * 100)
if current_mode == 5: volume = 0 # Mode 5 dimatikan suaranya
# --- Update UI LCD ---
vol_blocks = int((vol_pct / 100.0) * 10)
vol_bar = f"[{'='*vol_blocks}{' '*(10-vol_blocks)}] {vol_pct:02d}%"
if (speed_txt != last_speed_str or vol_bar != last_vol_str):
if time.ticks_diff(current_time, last_lcd_time) > 200:
lcd.move_to(0, 2)
lcd.putstr(f"Tempo: {speed_txt} ")
lcd.move_to(0, 3)
lcd.putstr(f"Vol: {vol_bar}")
last_lcd_time = current_time
last_speed_str, last_vol_str = speed_txt, vol_bar
# --- ANIMASI SINKRON ---
if time.ticks_diff(current_time, last_sync_time) >= speed_delay:
# 1: AMBULAN (Rotasi)
if current_mode == 1:
for i in range(NUM_LEDS):
np[i] = RED if (i + anim_counter) % NUM_LEDS < (NUM_LEDS // 2) else WHITE
np.write()
for i in range(6):
led_pins[i].value(1 if i == (anim_counter % 6) else 0)
if anim_counter % 8 == 0:
buzzer.freq(900 if siren_high else 700)
buzzer.duty_u16(volume)
siren_high = not siren_high
# 2: POLISI (Strobo Kiri-Kanan)
elif current_mode == 2:
is_left = (anim_counter // 3) % 2 == 0
for i in range(NUM_LEDS):
if is_left: np[i] = BLUE if i < (NUM_LEDS // 2) else OFF
else: np[i] = RED if i >= (NUM_LEDS // 2) else OFF
np.write()
for i in range(6):
led_pins[i].value(1 if (is_left and i < 3) or (not is_left and i >= 3) else 0)
if anim_counter % 3 == 0:
buzzer.freq(1200 if siren_high else 800)
buzzer.duty_u16(volume)
siren_high = not siren_high
# 3: PEMADAM (Kedip Berat)
elif current_mode == 3:
is_blink = (anim_counter // 12) % 2 == 0
current_color = WARM_RED if is_blink else ORANGE
for i in range(NUM_LEDS): np[i] = current_color
np.write()
for i in range(6): led_pins[i].value(1 if is_blink else 0)
if anim_counter % 12 == 0:
buzzer.freq(600 if siren_high else 450)
buzzer.duty_u16(volume)
siren_high = not siren_high
# 4: PENGAWAL VIP (Knight Rider)
elif current_mode == 4:
pos = anim_counter % 10
if pos > 5: pos = 10 - pos
for i in range(NUM_LEDS):
np[i] = WHITE if (i % 2 == anim_counter % 2) else BLUE
np.write()
for i in range(6): led_pins[i].value(1 if i == pos else 0)
if anim_counter % 2 == 0:
buzzer.freq(1500 if siren_high else 1300)
buzzer.duty_u16(volume)
siren_high = not siren_high
# 5: PATROLI MALAM (Sunyi)
elif current_mode == 5:
is_odd = (anim_counter // 8) % 2 == 0
for i in range(NUM_LEDS):
np[i] = BLUE if (i + (anim_counter//2)) % NUM_LEDS < (NUM_LEDS//2) else YELLOW
np.write()
for i in range(6):
led_pins[i].value(1 if (i % 2 == 0 and is_odd) or (i % 2 != 0 and not is_odd) else 0)
buzzer.duty_u16(0)
anim_counter += 1
last_sync_time = current_time