It/Hardware/Arduino Bell System

From msgwiki
Jump to navigation Jump to search

New Code

Developed by Walter DeMoss

Old Code

Developed by Micah Henney

Source code for the arduino bell system using the DS3231 RTC module and doorbells.

/**
 * An arduino program to ring the doorbells at MSG on the bell schedule.
 * Written by Micah Henney. Last updated February 2, 2024.
 *
 * Made for use with DS3231 RTC module. A similar program exists for the DS1302/DS1307
 * but that module keeps less accurate time due to an external temperature crystal.
 *
 * The RTC module should be connected through I2C using the default arduino I2C pins SDA and SCL.
 * Run Vcc on 5V and GND on GND. Do not connect SQW and 32K. 
 * To connect the bell system transmitter, take apart one of the doorbell buttons and solder pins
 * to the positve and negative battery terminals, as well as the top left pin of the button.
 * Connect the postive battery terminal to Vin (works on 5V, but made for 12V so Vin allows it to
 * receive more power if available), the negative terminal to GND, and the button lead to pin 8
 * (defined below PIN_TRIG). The arduino will pull the button lead LOW to trigger the transmitter.
 *
 * To set the schedule times, change the below lines mondayRingtimes and ringtimes. They are split
 * because VTE has a different schedule on Monday. CNX has the same ringtimes every day, so each
 * has the same values. Format is timeInDay(HourAs24, Minute, Second)
 *
 * To change the schedule ring times, the code must be reuploaded. Setting the current time can be
 * done through Serial. To change the schedule, change the times below, plug in the arduino and
 * upload the code. Then set the current time. To set the current time, plug in the arduino and
 * connect to the serial monitor. The board will print out the current set time, then how long it
 * will wait before ringing. If it is correct, no additional steps are required. If not, type the
 * current time in the serial monitor input in the format YYMMDDwHHMMSSx (the x at the end is
 * required, and all numbers except day of week must be 2 digit. Day of week Monday = 1) For
 * example, if the current time is Thursday, February 15 2024 at 2:17:00 PM, type "2402154141700x"
 * and enter (meaning year 2024, month 02, day 15, day of week 4, hour 14, minute 17, second 00)
 * Reset the board and confirm that it now shows the correct time. 
 * 
 * To pair the button transmitter with the alarm component of the doorbell, press the tone selector
 * on the doorbell until the desired tone plays. Hold the volume button for 4 seconds until the blue
 * light turns solidly on, then press the transmitter. The doorbell light will blink and turn off. 
 * Pairing is successful. 
 *
 * When not connected to serial, the arduino onboard LED will blink once for every 10 minutes
 * remaining until the next ringtime, up to a maximum of 20 blinks. It will sleep for about 8
 * seconds between each blink sequence.
 */

#include <DS3231-RTC.h>
#include <Adafruit_SleepyDog.h>

RTClib Rtc;
DS3231 ll_rtc;

#define countof(a) (sizeof(a) / sizeof(a[0]))
#define timeInDay(h, m, s) ((unsigned long)(h) * 60 * 60 + (m) * 60 + (s))
#define PIN_TRIG 8

unsigned long mondayRingtimes[] = {timeInDay(7, 45, 30), timeInDay(8, 0, 0), timeInDay(9, 10, 0), timeInDay(9, 15, 0), timeInDay(10, 23, 0), timeInDay(10, 55, 0), timeInDay(11, 0, 0), timeInDay(12, 10, 0), timeInDay(12, 55, 0), timeInDay(14, 0, 0), timeInDay(14, 30, 0), timeInDay(14, 35, 0), timeInDay(15, 40, 0), timeInDay(15, 55, 0), timeInDay(17, 0, 0)};
// {timeInDay(7, 15, 0), timeInDay(9, 50, 0), timeInDay(9, 55, 0), timeInDay(11, 10, 0), timeInDay(11, 45, 0), timeInDay(13, 0, 0), timeInDay(13, 5, 0), timeInDay(13, 35, 0), timeInDay(13, 40, 0), timeInDay(15, 35, 0)};
unsigned long ringtimes[] = {timeInDay(8, 0, 0), timeInDay(9, 10, 0), timeInDay(9, 15, 0), timeInDay(10, 23, 0), timeInDay(10, 55, 0), timeInDay(11, 0, 0), timeInDay(12, 10, 0), timeInDay(12, 55, 0), timeInDay(14, 0, 0), timeInDay(14, 30, 0), timeInDay(14, 35, 0), timeInDay(15, 40, 0), timeInDay(15, 55, 0), timeInDay(17, 0, 0)};
// {timeInDay(7, 15, 0), timeInDay(9, 50, 0), timeInDay(9, 55, 0), timeInDay(11, 10, 0), timeInDay(11, 45, 0), timeInDay(13, 0, 0), timeInDay(13, 5, 0), timeInDay(13, 35, 0), timeInDay(13, 40, 0), timeInDay(16, 25, 0)};

DateTime next;
bool timeJustSet = false;

void setup()
{
  pinMode(PIN_TRIG, OUTPUT);
  digitalWrite(PIN_TRIG, HIGH);
  delay(100);
  Serial.begin(9600);
  Wire.begin();

  delay(500);

  DateTime now = Rtc.now();
  DateTime test = DateTime(now.getUnixTime());
  Serial.print("Current time: ");
  printDateTime(now);
  Serial.println();
  delay(500);

  getNext(now);
}

void getNext(DateTime now)
{
  unsigned long cmp = timeInDay(now.getHour(), now.getMinute(), now.getSecond());
  if (now.getWeekDay() == 1) // Monday
  {
    Serial.println("Monday");
    if (cmp > mondayRingtimes[countof(mondayRingtimes) - 1])
    {
      // It's Monday night. Next ringtime is Tuesday morning
      next = timeOnDay(now, 2, ringtimes[0]);
      return;
    }
    else
    {
      // Use the next Monday ringtime
      for (int i = 0; i < countof(mondayRingtimes); i++)
      {
        if (cmp < mondayRingtimes[i])
        {
          next = timeOnDay(now, 1, mondayRingtimes[i]);
          return;
        }
      }
      Serial.println("This should never happen x1");
    }
  }
  else
  {
    if (now.getWeekDay() == 5) // Friday
    {
      Serial.println("Friday");
      if (cmp > ringtimes[countof(ringtimes) - 1])
      {
        next = timeOnDay(now, 1, mondayRingtimes[0]);
        return;
      }
      else
      {
        for (int i = 0; i < countof(ringtimes); i++)
        {
          if (cmp < ringtimes[i])
          {
            next = timeOnDay(now, 5, ringtimes[i]);
            return;
          }
        }
        Serial.println("This should never happen x2");
      }
    }
    else
    {
      // It's a Tuesday through Thursday. We can add one day and do tomorrow if not sometime today
      if (cmp > ringtimes[countof(ringtimes) - 1])
      {
        // Ring tomorrow morning
        next = timeOnDay(now, now.getWeekDay() + 1, ringtimes[0]);
        return;
      }
      else
      {
        // Get next ringtime today
        for (int i = 0; i < countof(ringtimes); i++)
        {
          if (cmp < ringtimes[i])
          {
            next = timeOnDay(now, now.getWeekDay(), ringtimes[i]);
            return;
          }
        }
        Serial.println("This should never happen x3");
      }
    }
  }
}

DateTime timeOnDay(DateTime reference, uint8_t dow, unsigned long time)
{
  if (reference.getWeekDay() == dow)
  {
    return DateTime(reference.getYear(), reference.getMonth(), reference.getDay(), time / 60 / 60, (time % 3600) / 60, time % 60, dow);
  }
  else
  {
    // Number of days until dow
    long addDays = (dow + 7 - reference.getWeekDay()) % 7;
    long diff = (addDays * 60 * 60 * 24);
    // Subtract seconds between 1970 and 2000
    // For some reason constructor assumes from Y2K
    DateTime sometimeInDay = DateTime(diff + reference.getY2kTime());

    DateTime ret = DateTime(sometimeInDay.getYear(), sometimeInDay.getMonth(), sometimeInDay.getDay(), time / 60 / 60, (time % 360) / 60, time % 60, dow);
    return ret;
  }
}

void printDateTime(DateTime dt)
{
  char datestring[50];
  dt.show_DateTime(datestring, sizeof(datestring));
  Serial.print(datestring);
}

void loop()
{
  DateTime now = Rtc.now();
  if(timeJustSet) {
    getNext(now);
    timeJustSet = false;
    delay(1000);
    return;
  }
  setTime();

  long timeToWait = next.getUnixTime() - now.getUnixTime();

  if (timeToWait < 0)
  {
    Serial.println("Ring");
    // Blink the on board LED as well
    digitalWrite(13, HIGH);
    digitalWrite(PIN_TRIG, LOW);
    delay(2000);
    digitalWrite(13, LOW);
    digitalWrite(PIN_TRIG, HIGH);
    getNext(now);
    Serial.print("Next ringtime: ");
    printDateTime(next);
    Serial.println();
  }
  else if (timeToWait < 10)
  {
    delay(50); // Delay for 1/20th of a second
  }
  else
  {
    // Blink onboard led once per 10 minutes remaining
    long minutesToWait = timeToWait / 60;
    Serial.print("Minutes to wait: ");
    Serial.print(minutesToWait);
    Serial.println();
    if (!Serial)
    {
      for (int i = 0; i < min((minutesToWait / 10) + 1, 20); i++)
      {
        digitalWrite(13, HIGH);
        delay(200);
        digitalWrite(13, LOW);
        delay(200);
      }
      Watchdog.sleep();
    }
    else
    {
      delay(2000);
    }
  }
}
void parseSetTimeInput(byte &year, byte &month, byte &date, byte &dOW,
                  byte &hour, byte &minute, byte &second)
{
  // Call this if you notice something coming in on
  // the serial port. The stuff coming in should be in
  // the order YYMMDDwHHMMSS, with an 'x' at the end.
  boolean gotString = false;
  char inChar;
  byte temp1, temp2;
  char inString[20];

  byte j = 0;
  while (!gotString)
  {
    if (Serial.available())
    {
      inChar = Serial.read();
      inString[j] = inChar;
      j += 1;
      if (inChar == 'x')
      {
        gotString = true;
      }
    }
    else
      return;
  }
  Serial.println(inString);
  // Read year first
  temp1 = (byte)inString[0] - 48;
  temp2 = (byte)inString[1] - 48;
  year = temp1 * 10 + temp2;
  // now month
  temp1 = (byte)inString[2] - 48;
  temp2 = (byte)inString[3] - 48;
  month = temp1 * 10 + temp2;
  // now date
  temp1 = (byte)inString[4] - 48;
  temp2 = (byte)inString[5] - 48;
  date = temp1 * 10 + temp2;
  // now Day of Week
  dOW = (byte)inString[6] - 48;
  // now hour
  temp1 = (byte)inString[7] - 48;
  temp2 = (byte)inString[8] - 48;
  hour = temp1 * 10 + temp2;
  // now minute
  temp1 = (byte)inString[9] - 48;
  temp2 = (byte)inString[10] - 48;
  minute = temp1 * 10 + temp2;
  // now second
  temp1 = (byte)inString[11] - 48;
  temp2 = (byte)inString[12] - 48;
  second = temp1 * 10 + temp2;
}

void setTime()
{
  if (!Serial)
    return;
  if (Serial.available())
  {
    byte year;
    byte month;
    byte date;
    byte dOW;
    byte hour;
    byte minute;
    byte second;
    parseSetTimeInput(year, month, date, dOW, hour, minute, second);

    ll_rtc.setClockMode(false); // set to 24h
    // setClockMode(true); // set to 12h

    ll_rtc.setYear(year);
    ll_rtc.setMonth(month);
    ll_rtc.setDate(date);
    ll_rtc.setDoW(dOW);
    ll_rtc.setHour(hour);
    ll_rtc.setMinute(minute);
    ll_rtc.setSecond(second);
    timeJustSet = true;
  }
}