It/Hardware/Arduino Bell System

From msgwiki
Revision as of 10:47, 14 February 2024 by Itvte (talk | contribs) (Created page with "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 ar...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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;
  }
}