import machine
import utime
import dht
from pico_i2c_lcd import I2cLcd
from ds1307 import DS1307
# ── I2C bus (shared by LCD and RTC) ──────────────────────────
i2c = machine.I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=400000)
# ── 16x2 LCD ─────────────────────────────────────────────────
# Run i2c.scan() in REPL to find your LCD address (common: 0x27 or 0x3F)
LCD_ADDR = 0x27
lcd = I2cLcd(i2c, LCD_ADDR, 2, 16)
# ── DS1307 RTC ───────────────────────────────────────────────
rtc = DS1307(i2c, addr=0x68)
# !! SET TIME ONCE: uncomment the line below, run, then re-comment !!
# rtc.datetime((2026, 4, 17, 4, 10, 0, 0, 0)) # (Y,M,D,wday,H,Min,Sec,_)
# ── Sensors ──────────────────────────────────────────────────
dht_sensor = dht.DHT22(machine.Pin(2))
trig = machine.Pin(3, machine.Pin.OUT)
echo = machine.Pin(4, machine.Pin.IN)
ldr_adc = machine.ADC(26)
pot_adc = machine.ADC(27)
# ── Outputs ──────────────────────────────────────────────────
relay = machine.Pin(5, machine.Pin.OUT)
buzzer = machine.Pin(6, machine.Pin.OUT)
led_r = machine.Pin(7, machine.Pin.OUT)
led_g = machine.Pin(8, machine.Pin.OUT)
led_b = machine.Pin(9, machine.Pin.OUT)
fan = machine.PWM(machine.Pin(10))
fan.freq(1000)
# ── Buttons — active LOW with internal pull-up ───────────────
btn = [
machine.Pin(11, machine.Pin.IN, machine.Pin.PULL_UP), # Relay override
machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_UP), # Buzzer override
machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP), # RGB cycle
machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_UP), # Fan override
]
# ── State variables ──────────────────────────────────────────
override = [False, False, False, False] # manual override per output
btn_prev = [1, 1, 1, 1] # previous button states (1=released)
rgb_mode = 0 # 0=off 1=red 2=green 3=blue 4=white
# LCD pages: 0=sensors, 1=outputs+alarm, 2=time
lcd_page = 0
page_timer = utime.ticks_ms()
PAGE_INTERVAL = 2500 # ms per page rotation
# ─────────────────────────────────────────────────────────────
# HELPER FUNCTIONS
# ─────────────────────────────────────────────────────────────
def set_fan(percent):
"""Set fan PWM speed 0–100 %"""
fan.duty_u16(int(percent / 100 * 65535))
def fan_percent():
"""Return current fan speed as int 0–100"""
return round(fan.duty_u16() / 65535 * 100)
def set_rgb(r, g, b):
led_r.value(r)
led_g.value(g)
led_b.value(b)
def measure_distance():
"""HC-SR04: returns distance in cm (999 on timeout)"""
trig.low()
utime.sleep_us(2)
trig.high()
utime.sleep_us(10)
trig.low()
t0 = utime.ticks_us()
while echo.value() == 0:
if utime.ticks_diff(utime.ticks_us(), t0) > 30000:
return 999
start = utime.ticks_us()
while echo.value() == 1:
if utime.ticks_diff(utime.ticks_us(), start) > 30000:
return 999
return utime.ticks_diff(utime.ticks_us(), start) * 0.0343 / 2
def get_time_str():
t = rtc.datetime()
return "{:02d}:{:02d}:{:02d}".format(t[4], t[5], t[6])
def get_date_str():
t = rtc.datetime()
return "{:02d}/{:02d}/{:04d}".format(t[2], t[1], t[0])
def log(msg):
"""Timestamped serial log"""
print("[{}] {}".format(get_time_str(), msg))
def ldr_label(val):
"""Convert raw ADC value to readable label"""
if val > 52000:
return "DARK"
elif val > 30000:
return "DIM "
else:
return "LIT "
def check_buttons():
global btn_prev, rgb_mode
for i in range(4):
cur = btn[i].value()
if btn_prev[i] == 1 and cur == 0: # falling edge = press
override[i] = not override[i]
names = ["RELAY", "BUZZER", "RGB", "FAN"]
if i == 2:
rgb_mode = (rgb_mode + 1) % 5 # cycle 0-4
log("MANUAL: RGB mode={}".format(rgb_mode))
else:
state = "ON" if override[i] else "AUTO"
log("MANUAL: {} override={}".format(names[i], state))
btn_prev[i] = cur
def apply_auto_rules(temp, distance, ldr_val):
"""
Rules (run only when override is OFF):
distance < 30 cm → Buzzer ON (proximity alert)
ldr_val > 52000 → Relay ON (room is dark)
temp > 35 °C → Fan 100 % (overheating)
temp > 30 °C → Fan 50 % (warm)
Returns alarm string for display.
"""
alarm = "OK "
# Relay
if not override[0]:
if ldr_val > 52000:
relay.high()
log("AUTO: RELAY ON — Dark LDR={}".format(ldr_val))
else:
relay.low()
# Buzzer
if not override[1]:
if distance < 30:
buzzer.high()
alarm = "PROX"
log("AUTO: BUZZER ON — D={:.1f}cm".format(distance))
else:
buzzer.low()
# RGB LED
if not override[2]:
set_rgb(0, 0, 0)
else:
modes = [(0,0,0),(1,0,0),(0,1,0),(0,0,1),(1,1,1)]
set_rgb(*modes[rgb_mode])
# Fan
if not override[3]:
if temp > 35.0:
set_fan(100)
alarm = "TEMP"
log("AUTO: FAN 100% — T={:.1f}C".format(temp))
elif temp > 30.0:
set_fan(50)
else:
set_fan(0)
else:
set_fan(100)
return alarm
def update_lcd(temp, hum, distance, ldr_val, alarm):
global lcd_page, page_timer
# Rotate page every PAGE_INTERVAL ms
if utime.ticks_diff(utime.ticks_ms(), page_timer) > PAGE_INTERVAL:
lcd_page = (lcd_page + 1) % 3
page_timer = utime.ticks_ms()
lcd.clear()
if lcd_page == 0:
# Sensors
row0 = "T:{:.0f}C H:{:.0f}% ".format(temp, hum)
row1 = "D:{:.0f}cm LDR:{}".format(distance, ldr_label(ldr_val))
lcd.move_to(0, 0); lcd.putstr(row0[:16])
lcd.move_to(0, 1); lcd.putstr(row1[:16])
elif lcd_page == 1:
# Output states
f = fan_percent()
rly = "Y" if relay.value() else "N"
buz = "Y" if buzzer.value() else "N"
row0 = "F:{:3d}% R:{} B:{} ".format(f, rly, buz)
row1 = "ALM:{} RGB:{} ".format(alarm, rgb_mode)
lcd.move_to(0, 0); lcd.putstr(row0[:16])
lcd.move_to(0, 1); lcd.putstr(row1[:16])
else:
# Date / time
lcd.move_to(0, 0); lcd.putstr(get_date_str()[:16])
lcd.move_to(0, 1); lcd.putstr(get_time_str()[:16])
# ─────────────────────────────────────────────────────────────
# STARTUP MESSAGE
# ─────────────────────────────────────────────────────────────
lcd.clear()
lcd.move_to(0, 0); lcd.putstr(" Dashboard ")
lcd.move_to(0, 1); lcd.putstr(" Initialising...")
utime.sleep(2)
lcd.clear()
print("=== Embedded Control Dashboard ===")
print("Booting...")
# ─────────────────────────────────────────────────────────────
# MAIN LOOP
# ─────────────────────────────────────────────────────────────
while True:
try:
# 1. Read all sensors
dht_sensor.measure()
temp = dht_sensor.temperature() # °C
hum = dht_sensor.humidity() # %
distance = measure_distance() # cm
ldr_val = ldr_adc.read_u16() # 0-65535
pot_val = pot_adc.read_u16() # 0-65535 (for future use)
# 2. Check button presses
check_buttons()
# 3. Apply automatic control rules
alarm = apply_auto_rules(temp, distance, ldr_val)
# 4. Update LCD display
update_lcd(temp, hum, distance, ldr_val, alarm)
# 5. Serial monitor output
print("T={:.1f}C H={:.1f}% D={:.1f}cm LDR={} POT={} FAN={}% RLY={} BUZ={}".format(
temp, hum, distance, ldr_val, pot_val,
fan_percent(), relay.value(), buzzer.value()))
except OSError as e:
print("Sensor read error:", e)
lcd.clear()
lcd.move_to(0, 0); lcd.putstr("SENSOR ERROR! ")
lcd.move_to(0, 1); lcd.putstr(str(e)[:16])
utime.sleep(1)
utime.sleep_ms(200)