Rohan Barnwal
Published © GPL3+

Never Lose Your Book Again - The Smart Book Holder That Buzz

Built with Arduino UNO R4 Wi-Fi + RTC + IR Sensors + Buzzer - because Mom said "Put that book back where it belongs!")

IntermediateFull instructions provided36
Never Lose Your Book Again - The Smart Book Holder That Buzz

Things used in this project

Hardware components

UNO R4 WiFi
Arduino UNO R4 WiFi
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Obstacle Avoidance IR Sensor
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Connections

Code

Code

Arduino
// UNO R4 WiFi — Continuous buzzer until previously-clear IR sensors return to DETECTED
// - IR sensors on pins 2,3,4
// - Buzzer on pin 11 (continuous tone while active)
// - Immediately prompt for HH:MM when any sensor is CLEAR; remember which sensors were clear
// - At alarm time, if any of those sensors are still CLEAR, start continuous buzzer
// - Buzzer stays ON until all those sensors become DETECTED again

#include <Arduino.h>
#include <RTC.h>

const int sensorPins[3] = {2, 3, 4};
const int buzzerPin = 11;

// true if IR module outputs LOW when an object is detected (common)
const bool SENSOR_ACTIVE_LOW = true;

// debounce for immediate prompt (ms)
const unsigned long CLEAR_DEBOUNCE_MS = 50UL;
// time print interval (ms)
const unsigned long TIME_PRINT_MS = 1000UL;

volatile bool rtcAlarmFired = false;

unsigned long lastTimePrintMs = 0;
unsigned long clearStartMs = 0;
bool waitingForTarget = false;
int targetHour = -1;
int targetMinute = -1;

// mask of sensors that were CLEAR at prompt time (bit0 = sensor1)
uint8_t clearMaskAtPrompt = 0;

// buzzer active state: when true, continuous tone is ON
bool buzzerActive = false;

// minimal alarm callback
void alarm_cbk() {
  rtcAlarmFired = true;
}

void setup() {
  Serial.begin(115200);
  while (!Serial) { /* wait for serial */ }

  Serial.println();
  Serial.println("UNO R4 WiFi - Continuous buzzer until IR returns to DETECTED");
  Serial.print("Sensor polarity: ");
  Serial.println(SENSOR_ACTIVE_LOW ? "ACTIVE LOW (LOW = detected)" : "ACTIVE HIGH (HIGH = detected)");
  Serial.println("When any sensor is CLEAR you'll be asked to enter HH:MM.");
  Serial.println();

  if (!RTC.begin()) {
    Serial.println("WARNING: RTC.begin() failed. Make sure UNO R4 WiFi core is installed.");
  } else {
    Serial.println("RTC initialized (RTC.begin OK).");
    // Optionally set RTC here if needed.
  }

  for (int i = 0; i < 3; ++i) pinMode(sensorPins[i], INPUT);
  pinMode(buzzerPin, OUTPUT);
  noTone(buzzerPin); // ensure buzzer off

  // initial print
  printCurrentTimeAndStatus();
  lastTimePrintMs = millis();
}

void loop() {
  unsigned long nowMs = millis();

  // A) print current time + status each second
  if (nowMs - lastTimePrintMs >= TIME_PRINT_MS) {
    printCurrentTimeAndStatus();
    lastTimePrintMs = nowMs;
  }

  // B) check current sensor clear status for immediate prompt
  bool curClear[3];
  bool anyClear = false;
  for (int i = 0; i < 3; ++i) {
    curClear[i] = !readSensor(i); // readSensor: true=DETECTED -> invert => CLEAR
    if (curClear[i]) anyClear = true;
  }

  if (anyClear) {
    if (clearStartMs == 0) clearStartMs = nowMs;
    else if (!waitingForTarget && (nowMs - clearStartMs >= CLEAR_DEBOUNCE_MS)) {
      // build mask
      clearMaskAtPrompt = 0;
      for (int i = 0; i < 3; ++i) if (curClear[i]) clearMaskAtPrompt |= (1 << i);

      Serial.println();
      Serial.print("Sensors clear now: ");
      for (int i = 0; i < 3; ++i) {
        Serial.print("S"); Serial.print(i+1); Serial.print(":"); Serial.print(curClear[i] ? "CLEAR" : "DETECTED");
        if (i < 2) Serial.print("  ");
      }
      Serial.println();
      Serial.println("Enter target time (HH:MM, 24-hour) at which I will check these sensor(s):");
      Serial.print("Time> ");
      waitingForTarget = true;
      // keep clearStartMs so user can cancel with empty input if desired
    }
  } else {
    clearStartMs = 0;
  }

  // C) accept Serial time if waiting
  if (waitingForTarget && Serial.available()) {
    String line = Serial.readStringUntil('\n');
    line.trim();
    if (line.length() == 0) {
      Serial.println("Cancelled (empty).");
      waitingForTarget = false;
      clearMaskAtPrompt = 0;
    } else {
      int h, m;
      if (parseTimeString(line, h, m)) {
        targetHour = h; targetMinute = m;
        Serial.print("Target time set: ");
        printTwoDigits(targetHour); Serial.print(":"); printTwoDigits(targetMinute); Serial.println();

        // prepare alarm time + match
        RTCTime alarmTime;
        alarmTime.setHour(targetHour);
        alarmTime.setMinute(targetMinute);
        alarmTime.setSecond(0);

        AlarmMatch match;
        match.addMatchHour();
        match.addMatchMinute();

        if (RTC.setAlarmCallback(alarm_cbk, alarmTime, match)) {
          Serial.print("Alarm registered for ");
          printTwoDigits(targetHour); Serial.print(":"); printTwoDigits(targetMinute);
          Serial.print("  checking mask 0b");
          for (int b = 2; b >= 0; --b) Serial.print((clearMaskAtPrompt >> b) & 1);
          Serial.println();
        } else {
          Serial.println("ERROR: Failed to set RTC alarm. Ensure RTC time is set and RTC.begin() succeeded.");
          clearMaskAtPrompt = 0;
        }
        waitingForTarget = false;
      } else {
        Serial.println("Invalid format. Use HH:MM (24-hour). Example: 01:28 or 13:28");
        Serial.print("Time> ");
      }
    }
  }

  // D) Handle RTC alarm when fired
  if (rtcAlarmFired) {
    noInterrupts();
    rtcAlarmFired = false;
    interrupts();

    RTCTime nowt;
    RTC.getTime(nowt);
    int hh = nowt.getHour();
    int mm = nowt.getMinutes();
    int ss = nowt.getSeconds();
    Serial.print("\nRTC alarm fired at ");
    printTwoDigits(hh); Serial.print(":"); printTwoDigits(mm); Serial.print(":"); printTwoDigits(ss); Serial.println();

    // If there is no mask, nothing to check
    if (clearMaskAtPrompt == 0) {
      Serial.println("No sensors were recorded CLEAR at prompt time — nothing to check.");
    } else {
      // check only sensors in mask
      bool anyStillClear = false;
      for (int i = 0; i < 3; ++i) {
        if (clearMaskAtPrompt & (1 << i)) {
          bool detectedNow = readSensor(i); // true=DETECTED
          Serial.print("Sensor"); Serial.print(i+1); Serial.print(" (was clear): ");
          Serial.println(detectedNow ? "DETECTED now" : "STILL CLEAR");
          if (!detectedNow) anyStillClear = true;
        }
      }

      if (anyStillClear) {
        Serial.println("One or more sensors that were CLEAR are STILL CLEAR -> starting continuous buzzer!");
        startContinuousBuzzer(); // starts tone and sets buzzerActive
      } else {
        Serial.println("All sensors that were CLEAR are now DETECTED -> no buzzer.");
      }
    }

    Serial.println("------------------------------------");
  }

  // E) If buzzerActive, monitor the same mask and stop buzzer only when all those sensors become DETECTED
  if (buzzerActive) {
    bool allDetectedNow = true;
    for (int i = 0; i < 3; ++i) {
      if (clearMaskAtPrompt & (1 << i)) {
        if (!readSensor(i)) { // still CLEAR
          allDetectedNow = false;
          break;
        }
      }
    }
    if (allDetectedNow) {
      Serial.println("All previously-clear sensors are now DETECTED -> stopping buzzer.");
      stopContinuousBuzzer();
      // clear mask as handled
      clearMaskAtPrompt = 0;
    }
    // otherwise keep buzzing; we check again next loop iteration
  }

  delay(10);
}

// ---------------- Helper functions ----------------

bool readSensor(int idx) {
  int raw = digitalRead(sensorPins[idx]);
  if (SENSOR_ACTIVE_LOW) return (raw == LOW);
  else                  return (raw == HIGH);
}

void printCurrentTimeAndStatus() {
  RTCTime t;
  RTC.getTime(t);
  int hh = t.getHour();
  int mm = t.getMinutes();
  int ss = t.getSeconds();

  int disp12 = hh % 12; if (disp12 == 0) disp12 = 12;
  String ampm = (hh < 12) ? "AM" : "PM";

  Serial.print("Time: ");
  printTwoDigits(hh); Serial.print(":"); printTwoDigits(mm); Serial.print(":"); printTwoDigits(ss);
  Serial.print(" (");
  if (disp12 < 10) Serial.print('0');
  Serial.print(disp12); Serial.print(":"); printTwoDigits(mm); Serial.print(" "); Serial.print(ampm);
  Serial.print(")  ");

  for (int i = 0; i < 3; ++i) {
    bool s = readSensor(i);
    Serial.print("S"); Serial.print(i+1); Serial.print(":");
    Serial.print(s ? "DETECTED" : "CLEAR");
    if (i < 2) Serial.print("  ");
  }
  Serial.println();
}

bool parseTimeString(const String &in, int &outH, int &outM) {
  int colon = in.indexOf(':');
  if (colon <= 0) return false;
  String sh = in.substring(0, colon);
  String sm = in.substring(colon + 1);
  sh.trim(); sm.trim();
  if (sh.length() == 0 || sm.length() == 0) return false;
  int h = sh.toInt();
  int m = sm.toInt();
  if (h < 0 || h > 23 || m < 0 || m > 59) return false;
  outH = h; outM = m;
  return true;
}

// Start continuous buzzer: set tone once and set buzzerActive flag
void startContinuousBuzzer() {
  if (!buzzerActive) {
    tone(buzzerPin, 2000); // continuous 2kHz tone (stays until noTone)
    buzzerActive = true;
  }
}

// Stop continuous buzzer: call noTone() and clear flag
void stopContinuousBuzzer() {
  if (buzzerActive) {
    noTone(buzzerPin);
    buzzerActive = false;
  }
}

// print number with two digits
void printTwoDigits(int x) {
  if (x < 10) Serial.print('0');
  Serial.print(x);
}

Credits

Rohan Barnwal
37 projects • 35 followers
Rohan Barnwal - maker, hacker, tech enthusiast. I explore new tech & find innovative solutions. See my projects on hackster.io!

Comments