Scheduled Appliance Controller

Scheduled Appliance Controller

HARDWARE REQUIRED:

  • PICUNO Microcontroller board
  • 1 × 16x2 I2C LCD Display
  • 1 × DS1302 Real Time Clock Module
  • 1 × 5V Relay Module
  • 1 × LED
  • 1 × 220Ω resistor (Current-limiting resistor)
  • Jumper wires
  • USB cable
  • 1 × 4xAA Battery pack (with fresh or rechargeable batteries)

DESCRIPTION:

This project creates a simple but effective smart timer, like those used for home appliances. The system uses a DS1302 Real-Time Clock (RTC) module to keep accurate track of the current time.

A user can define a specific "ON" time and "OFF" time directly in the code. The PicUNO continuously checks the time from the RTC and compares it to the schedule. When the current time matches the "ON" time, it activates a relay module. When the "OFF" time is reached, it deactivates the relay. The relay's switched terminals can control an external circuit, represented here by an LED. An I2C LCD provides a constant, real-time display of the current time and the relay's status (ON/OFF).

LIBRARIES REQUIRED:
For C / Arduino IDE:
Ds1302 by Rafa Couto: The driver library for the DS1302 RTC Module.
Wire.h: Manages I2C communication (usually included by default).
LiquidCrystal_I2C.h: The driver library for the I2C LCD module.

For Micropython / Thonny IDE:
ds1302.py: The custom library file for the RTC, saved to the PICUNO board.
i2c_lcd.py: The custom library file saved to the PICUNO board.
The code also uses the built-in machine and time modules.

CIRCUIT DIAGRAM:

Scheduled Appliance Controller
  • Connect the LCD Module's GND pin to a GND pin on board.
  • Connect the LCD Module's VCC pin to positive terminal (+) of the 4xAA Battery Pack.
  • Connect the LCD Module's SDA pin GPIO 4 (SDA Pin on PICUNO).
  • Connect the LCD Module's SCL pin GPIO 5 (SCL Pin on PICUNO).
  • Connect the negative terminal (-) of the 4xAA Battery Pack to GND pin on PICUNO board to create common ground.
DS1302 RTC Module:
  • Connect the VCC pin to 5V pin on the board.
  • Connect the GND pin to GND pin on board.
  • Connect the CLK pin to GPIO 8.
  • Connect the DAT pin to GPIO 9.
  • Connect the RST pin to GPIO 10.
Relay Module:
  • Connect the GND pin to a GND pin on PICUNO.
  • Connect the VCC pin to the 5V pin on PICUNO.
  • Connect the IN (Signal) pin to GPIO 7.
  • Connect the COM (Common) Screw terminal on the relay to 3.3V.
  • Connect the NO (Normally Open) Screw terminal on the relay to longer leg (anode) of the LED.
  • Connect the shorter leg (cathode) of the LED to one end of 220Ω resistor.
  • Connect the other end of the resistor to GND.

SCHEMATIC:

LCD VCC → 5V

LCD GND → GND

LCD SDA → GPIO 4 (Board SDA Pin)

LCD SCL → GPIO 5 (Board SCL Pin)

RTC VCC → 5V

RTC GND → GND

RTC CLK → GPIO 8

RTC DAT → GPIO 9

RTC RST → GPIO 10

Relay VCC → 5V

Relay GND → GND

Relay IN → GPIO 7

Relay COM → 3.3V

Relay NO → anode of LED

Cathode of LED → 220Ω resistor → GND

CODE -- C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Ds1302.h> 

LiquidCrystal_I2C lcd(0x27, 16, 2);

// Define pins for the DS1302 module
const int RST_PIN = 10;
const int DAT_PIN = 9;
const int CLK_PIN = 8;

Ds1302 rtc(RST_PIN, CLK_PIN, DAT_PIN);

const int RELAY_PIN = 7;

// --- Schedule Configuration ---
const int ON_HOUR = 7, ON_MINUTE = 0, ON_SECOND = 0;
const int OFF_HOUR = 7, OFF_MINUTE = 5, OFF_SECOND = 0;

void setup() {
  // Initialize components
  lcd.init();
  lcd.backlight();
  rtc.init(); // Initialize the RTC
  
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // Start with relay off

  if (rtc.isHalted()) {
    // Create a DateTime object with the current time
    Ds1302::DateTime dt = {
        .year = 25,         // Year (00-99) -> 2025
        .month = 8,         // Month
        .day = 16,          // Day
        .hour = 15,         // Hour (24-hour format)
        .minute = 27,       // Minute
        .second = 48,       // Second
        .dow = 7            // Day of week (1=Sun, 2=Mon... 7=Sat)
    };
    // Set the time on the RTC module
    rtc.setDateTime(&dt);
  }

  lcd.print("Scheduler Ready");
  delay(2000);
}

void loop() {
  // 1. Get the current time from the RTC
  Ds1302::DateTime now;
  rtc.getDateTime(&now);

  // 2. Check against the schedule
  if (now.hour == ON_HOUR && now.minute == ON_MINUTE && now.second == ON_SECOND) {
    if (digitalRead(RELAY_PIN) == LOW) { // Check if not already on
      digitalWrite(RELAY_PIN, HIGH);
    }
  }

  if (now.hour == OFF_HOUR && now.minute == OFF_MINUTE && now.second == OFF_SECOND) {
    if (digitalRead(RELAY_PIN) == HIGH) { // Check if not already off
      digitalWrite(RELAY_PIN, LOW);
    }
  }

  // 3. Display status on the LCD
  lcd.setCursor(0, 0);
  char timeString[9];
  sprintf(timeString, "%02d:%02d:%02d", now.hour, now.minute, now.second);
  lcd.print(timeString);

  lcd.setCursor(0, 1);
  lcd.print("Relay Status: ");
  lcd.print(digitalRead(RELAY_PIN) == HIGH ? "ON " : "OFF");

  delay(500); 
}
Ds1302 rtc(RST_PIN, CLK_PIN, DAT_PIN); - This creates the RTC object. Note the specific pin order required by this library: Reset, Clock, Data.

if (rtc.isHalted()) - The code to set the time will only run if the RTC chip has lost all power and its internal clock has stopped. This prevents the time from being reset every time you power on the PicUNO.

rtc.setDateTime(&dt); - This is the command used to send the DateTime structure to the RTC module to set its time.

rtc.getDateTime(&now); - In the main loop, this command fetches the current time from the RTC and stores it in the now variable. You can then access the parts of the time with now.hour, now.minute, etc.

CODE -- PYTHON:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from machine import Pin, I2C
import time
from i2c_lcd import I2cLcd
from ds1302 import DS1302

# I2C LCD Display
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
lcd = I2cLcd(i2c, 0x27, 2, 16)

# DS1302 RTC Module
rtc = DS1302(Pin(8), Pin(9), Pin(10))

relay = Pin(7, Pin.OUT)

ON_TIME = (7, 0, 0)  # 7:00:00 AM

OFF_TIME = (7, 5, 0) # 7:05:00 AM

#Format: (Year, Month, Day, Weekday(1=Mon), Hour, Minute, Second)
#rtc.date_time((2025, 8, 16, 6, 12, 35, 0)) 

# --- Main Program ---
print("Scheduled Appliance Controller Initialized.")
relay.low() 
lcd.clear()
lcd.putstr("Scheduler Ready")
time.sleep(2)

while True:
    (year, month, day, weekday, hour, minute, second) = rtc.date_time()
    
    current_time_tuple = (hour, minute, second)
    
    # 2. Check against the schedule
    if current_time_tuple == ON_TIME:
        if relay.value() == 0: # Check if it's not already on
            print(f"ON Time Reached! Activating relay at {hour:02d}:{minute:02d}:{second:02d}")
            relay.high()
            
    if current_time_tuple == OFF_TIME:
        if relay.value() == 1: # Check if it's not already off
            print(f"OFF Time Reached! Deactivating relay at {hour:02d}:{minute:02d}:{second:02d}")
            relay.low()

    # 3. Display status on the LCD
    # Format the time and date strings with leading zeros
    time_string = f"{hour:02d}:{minute:02d}:{second:02d}"
    relay_status = "ON " if relay.value() == 1 else "OFF"
    
    # Display on LCD
    lcd.move_to(0, 0)
    lcd.putstr(time_string)
    lcd.move_to(0, 1)
    lcd.putstr(f"Relay Status: {relay_status}")
    
    time.sleep(0.5)
rtc.date_time((...)) - This is the one-time command used to set the clock. You should uncomment it, fill in the current time, run the script once to set the RTC, and then comment it out again.

if current_time_tuple == ON_TIME: - This is the core logic of the scheduler. It directly compares the current time with the target "ON" time. If they match exactly, it activates the relay.

f"{hour:02d}" - This is a Python f-string used for formatting. The :02d part tells it to format the number as a 2-digit decimal, adding a leading zero if necessary (e.g., 7 becomes 07).