Alexander
Published

Arduino Clock with Neopixel Ring Animation

Yet another useless Arduino clock, but i love it.

BeginnerFull instructions provided10 hours78,712
Arduino Clock with Neopixel Ring Animation

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
DS3231 RTC module for Arduino
×1
NeoPixel Ring: WS2812 5050 RGB LED
Adafruit NeoPixel Ring: WS2812 5050 RGB LED
×1
0.56" 4-digits 7 segment display
×1
Resistor 220 ohm
Resistor 220 ohm
×8
440 Ohm resistor for neopixel ring
×1
Capacitor 1000 µF
Capacitor 1000 µF
×2
push button
×2
Capacitor 100 nF
Capacitor 100 nF
×2
AC DC 5v power supply 1A
×1
Shift register, 74HC595
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

The schematics

Code

The clock source code

Arduino
Trying to adjust my C/C++ coding style.
//The clock with 4x7-segment indicator with a shift register and neopixel rgb 24 led ring @ arduino nano
#include <EEPROM.h>
#include <Wire.h>
#include <DS3232RTC.h>
#include <Time.h>
#include <TimeLib.h>

// The 4 7-segment indicator with the shift register
const byte digit_pin[4]   = { 14, 15, 16, 17 };
const byte dataPIN_14  = 7;
const byte latchPIN_12 = 8;
const byte clockPIN_11 = 9;

// Neopixel ring
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif
const byte NEOPIXEL = 10;                           // Pin of Neopixel Ring
const byte RingSize = 24;
const byte HZ = 10;                                 // Redraw neopixel ring frequency (times per second)
    
const byte BTN_MENU_PIN = 2;
const byte BTN_INCR_PIN = 3;

Adafruit_NeoPixel strip = Adafruit_NeoPixel(RingSize, NEOPIXEL, NEO_GRB + NEO_KHZ800);

//------------------------------------------ Configuration data ------------------------------------------------
/* Config record in the EEPROM has the following format:
  uint32_t ID                           each time increment by 1
  struct cfg                            config data
  byte CRC                              the checksum
*/
struct cfg {
  uint16_t  alarm_time;                             // Alarm time in minuites from midnight
  byte      wday_mask;                              // Week day alarm mask 0 - sunday, 7 - saturday
  bool      active;                                 // Whether the alarm is active
  byte      nl_br;                                  // The night light brightness 0 - off, [1 - 6]
  byte      morning;                                // Morning time (10-minutes intervals), startinf neopixel animation
  byte      evening;                                // Evening time (10-minutes)
};

class CONFIG {
  public:
    CONFIG() {
      can_write     = false;
      buffRecords   = 0;
      rAddr = wAddr = 0;
      eLength       = 0;
      nextRecID     = 0;
      byte rs = sizeof(struct cfg) + 5;               // The total config record size
      // Select appropriate record size; The record size should be power of 2, i.e. 8, 16, 32, 64, ... bytes
      for (record_size = 8; record_size < rs; record_size <<= 1);
    }
    void init();
    void load(void);
    void getConfig(struct cfg &Cfg);                  // Copy config structure from this class
    void updateConfig(struct cfg &Cfg);               // Copy updated config into this class
    bool save(void);                                  // Save current config copy to the EEPROM
    bool saveConfig(struct cfg &Cfg);                 // write updated config into the EEPROM
    
  private:
    void     defaultConfig(void);
    struct   cfg Config;
    bool     readRecord(uint16_t addr, uint32_t &recID);
    bool     can_write;                               // The flag indicates that data can be saved
    byte     buffRecords;                             // Number of the records in the outpt buffer
    uint16_t rAddr;                                   // Address of thecorrect record in EEPROM to be read
    uint16_t wAddr;                                   // Address in the EEPROM to start write new record
    uint16_t eLength;                                 // Length of the EEPROM, depends on arduino model
    uint32_t nextRecID;                               // next record ID
    byte     record_size;                             // The size of one record in bytes
};

 // Read the records until the last one, point wAddr (write address) after the last record
void CONFIG::init(void) {
  eLength = EEPROM.length();
  uint32_t recID;
  uint32_t minRecID = 0xffffffff;
  uint16_t minRecAddr = 0;
  uint32_t maxRecID = 0;
  uint16_t maxRecAddr = 0;
  byte     records = 0;

  nextRecID = 0;

  // read all the records in the EEPROM find min and max record ID
  for (uint16_t addr = 0; addr < eLength; addr += record_size) {
    if (readRecord(addr, recID)) {
      ++records;
      if (minRecID > recID) {
        minRecID   = recID;
        minRecAddr = addr;
      }
      if (maxRecID < recID) {
        maxRecID   = recID;
        maxRecAddr = addr;
      }
    } else {
      break;
    }
  }

  if (records == 0) {
    wAddr = rAddr = 0;
    can_write = true;
    return;
  }

  rAddr = maxRecAddr;
  if (records < (eLength / record_size)) {            // The EEPROM is not full
    wAddr = rAddr + record_size;
    if (wAddr > eLength) wAddr = 0;
  } else {
    wAddr = minRecAddr;
  }
  can_write = true;
}

void CONFIG::getConfig(struct cfg &Cfg) {
  memcpy(&Cfg, &Config, sizeof(struct cfg));
}

void CONFIG::updateConfig(struct cfg &Cfg) {
  memcpy(&Config, &Cfg, sizeof(struct cfg));
}

bool CONFIG::saveConfig(struct cfg &Cfg) {
  updateConfig(Cfg);
  return save();                                      // Save new data into the EEPROM
}

bool CONFIG::save(void) {
  if (!can_write) return can_write;
  if (nextRecID == 0) nextRecID = 1;

  uint16_t startWrite = wAddr;
  uint32_t nxt = nextRecID;
  byte summ = 0;
  for (byte i = 0; i < 4; ++i) {
    EEPROM.write(startWrite++, nxt & 0xff);
    summ <<=2; summ += nxt;
    nxt >>= 8;
  }
  byte* p = (byte *)&Config;
  for (byte i = 0; i < sizeof(struct cfg); ++i) {
    summ <<= 2; summ += p[i];
    EEPROM.write(startWrite++, p[i]);
  }
  summ ++;                                            // To avoid empty records
  EEPROM.write(wAddr+record_size-1, summ);

  rAddr = wAddr;
  wAddr += record_size;
  if (wAddr > EEPROM.length()) wAddr = 0;
  return true;
}

void CONFIG::load(void) {
  bool is_valid = readRecord(rAddr, nextRecID);
  nextRecID ++;
  if (!is_valid) defaultConfig();
  return;
}

bool CONFIG::readRecord(uint16_t addr, uint32_t &recID) {
  byte Buff[record_size];

  for (byte i = 0; i < record_size; ++i) 
    Buff[i] = EEPROM.read(addr+i);
  
  byte summ = 0;
  for (byte i = 0; i < sizeof(struct cfg) + 4; ++i) {

    summ <<= 2; summ += Buff[i];
  }
  summ ++;                                            // To avoid empty fields
  if (summ == Buff[record_size-1]) {                  // Checksumm is correct
    uint32_t ts = 0;
    for (char i = 3; i >= 0; --i) {
      ts <<= 8;
      ts |= Buff[byte(i)];
    }
    recID = ts;
    memcpy(&Config, &Buff[4], sizeof(struct cfg));
    return true;
  }
  return false;
}

void CONFIG::defaultConfig(void) {
  Config.alarm_time = 0;
  Config.wday_mask  = 0;
  Config.active     = false;
  Config.nl_br      = 3;
  Config.morning    = 60;                             // 10:00
  Config.evening    = 126;                            // 21:00
}

//------------------------------------------ class BUTTON ------------------------------------------------------
class BUTTON {
  public:
    BUTTON(byte ButtonPIN, uint16_t timeout_ms = 3000) {
      buttonPIN = ButtonPIN; 
      pt = tickTime = 0; 
      overPress = timeout_ms;
    }
    void init(void) { pinMode(buttonPIN, INPUT_PULLUP); }
    void setTimeout(uint16_t timeout_ms = 3000) { overPress = timeout_ms; }
    byte buttonCheck(void);
    byte intButtonStatus(void) { byte m = mode; mode = 0; return m; }
    bool buttonTick(void);
    void buttonCnangeINTR(void);
  private:
    const uint16_t shortPress = 900;                // If the button was pressed less that this timeout, we assume the short button press
    const uint16_t tickTimeout = 200;               // Period of button tick, while tha button is pressed
    const byte bounce = 50;                         // Bouncing timeout (ms)  
    uint16_t overPress;                             // Maxumum time in ms the button can be pressed
    volatile byte mode;                             // The button mode: 0 - not presses, 1 - short press, 2 - long press
    volatile uint32_t pt;                           // Time in ms when the button was pressed (press time)
    volatile uint32_t tickTime;                     // The time in ms when the button Tick was set
    byte buttonPIN;                                 // The pin number connected to the button
};

byte BUTTON::buttonCheck(void) {                    // Check the button state, called each time in the main loop

  mode = 0;
  bool keyUp = digitalRead(buttonPIN);              // Read the current state of the button
  uint32_t now_t = millis();
  if (!keyUp) {                                     // The button is pressed
    if ((pt == 0) || (now_t - pt > overPress)) pt = now_t;
  } else {
    if (pt == 0) return 0;
    if ((now_t - pt) < bounce) return 0;
    if ((now_t - pt) > shortPress)                  // Long press
      mode = 2;
    else
      mode = 1;
    pt = 0;
  } 
  return mode;
}

bool BUTTON::buttonTick(void) {                     // When the button pressed for a while, generate periodical ticks

  bool keyUp = digitalRead(buttonPIN);              // Read the current state of the button
  uint32_t now_t = millis();
  if (!keyUp && (now_t - pt > shortPress)) {        // The button have been pressed for a while
    if (now_t - tickTime > tickTimeout) {
       tickTime = now_t;
       return (pt != 0);
    }
  } else {
    if (pt == 0) return false;
    tickTime = 0;
  } 
  return false;
}

void BUTTON::buttonCnangeINTR(void) {               // Interrupt function, called when the button status changed
  
  bool keyUp = digitalRead(buttonPIN);
  unsigned long now_t = millis();
  if (!keyUp) {                                     // The button has been pressed
    if ((pt == 0) || (now_t - pt > overPress)) pt = now_t; 
  } else {
    if ((now_t - pt) < bounce) return;
    if (pt > 0) {
      if ((now_t - pt) < shortPress) mode = 1;      // short press
        else mode = 2;                              // long press
      pt = 0;
    }
  }
}

//------------------------------------------ class LED display with the shift register -------------------------
class DSPL {
  public:
    DSPL(const byte data, const byte latch, const byte clck, const byte dgts[4]) {
       dataPIN  = data;
       latchPIN = latch;
       clockPIN = clck;
       for (byte i = 0; i < 4; ++i) digit_pin[i] = dgts[i];
    }
    void init(void);                                // Initialize the clock display
    void show(byte dot, byte displayMask = 0xf);    // Should be called periodicaly to redraw the data on the LED display
    void showTime(byte Hour, byte Minute);          // Set time to the display
    void showDash(void);                            // show dash line if the alarm is not setup
    void showWdayGeneral(byte wday_mask);
    void showWdayFull(byte wday_mask);
  private:
    byte dataPIN, latchPIN, clockPIN;               // The shift register interface pins
    byte digit_pin[4];                              // The pin of segmants
    byte symbol[4];                                 // The symbols to be displyed
    const byte hex[16] = {  0b11111100, 0b01100000, 0b11011010, 0b11110010, 0b01100110, 
                            0b10110110, 0b10111110, 0b11100000, 0b11111110, 0b11110110,
                            0b11101110, 0b00111110, 0b10011100, 0b01111010, 0b10011110, 0b10001110};
    };
    const byte wday[7] = {  0b00000100, 0b10000000, 0b01000000, 0b00000010, 0b00100000, 0b00010000, 0b00001000 };

void DSPL::init(void) {
  pinMode(dataPIN,  OUTPUT);
  pinMode(latchPIN, OUTPUT);
  pinMode(clockPIN, OUTPUT);

  for (int i = 0; i < 4; ++i) {
    pinMode(digit_pin[i], OUTPUT);
    digitalWrite(digit_pin[i], HIGH);
  }
}

void DSPL::show(byte dot, byte displayMask) {
  byte mask = 1;
  for (byte d = 0; d < 4; ++d) {                    // Print out digit from right to left
    byte s = symbol[d];
    if (dot & mask) s |= 1;
    if (displayMask & mask) {
      digitalWrite(latchPIN_12, LOW);
      shiftOut(dataPIN_14, clockPIN_11, LSBFIRST, s);    
      digitalWrite(latchPIN_12, HIGH);
    
      digitalWrite(digit_pin[d], LOW);
      delayMicroseconds(50);
      digitalWrite(digit_pin[d], HIGH);
    }
    mask <<= 1;
  }
}

void DSPL::showTime(byte Hour, byte Minute) {
  byte digit;
  
  for (byte d = 0; d < 4; ++d) {                    // Print out digit from right to left
    if (d < 2) {                                    // Minutes
      digit = Minute % 10;
      Minute /= 10;
    } else {                                        // Hours
      digit = Hour % 10;
      Hour /= 10;
    }
    symbol[d] = hex[digit];
  }
}

void DSPL::showDash(void) {
  for (byte d = 0; d < 4; ++d)
    symbol[d] = 0b00000010;                         // dash
}

void DSPL::showWdayGeneral(byte wday_mask) {
  byte wd = 0;
  byte m  = 1;
  symbol[3] = hex[10] | 1;                          // 'A.'
  switch (wday_mask) {
    case 0b00111110:                                // Working days
      symbol[2] = hex[1];
      symbol[1] = 0b00000010;                       // dash
      symbol[0] = hex[5];
      break;
    case 0b00011110:
      symbol[2] = hex[1];
      symbol[1] = 0b00000010;                       // dash
      symbol[0] = hex[4];
      break;
    case 0b01111111:
      symbol[2] = hex[10];                          // A
      symbol[1] = symbol[0] = 0b00011100;           // L
      break;
    case 0:
      showDash();
    default:                                        // 0 or unknown
      for (byte i = 0; i <= 6; ++i) {
        if (m & wday_mask) wd |= wday[i];
        m <<= 1;
      }
      symbol[2] = wd;
      break;
  }
}

void DSPL::showWdayFull(byte wday_mask) {
  symbol[3] = hex[10] | 1;                          // 'A.'
  symbol[2] = hex[wday_mask / 16];
  symbol[1] = hex[wday_mask % 16] | 1;
  byte wd = 0;
  byte m  = 1;
  for (byte i = 0; i <= 6; ++i) {
    if (m & wday_mask) wd |= wday[i];
    m <<= 1;
  }
  symbol[0] = wd;
}

//------------------------------------------ class NEOPIXEL ANIMATION ------------------------------------------
class ANIMATION {
  public:
    ANIMATION() {
      for (byte i = 0; i < 3; ++i)
        rgb[i] = random(32) << 2;
    }
    void          switchOffRing(void);
    uint32_t      Wheel(byte WheelPos);
    virtual void  show(uint16_t S,  uint32_t Color) = 0;
    virtual void  clean(uint16_t S, uint32_t Color) = 0;
    virtual void  handle(byte k)                    { }
  protected:
    void flashLabels(uint16_t S, byte pos = 255);
    void fadeRing(uint32_t Color, byte shift);
    void initXcolor(void);                          // Initialize the color of cross-clock pixels
    byte rgb[3];                                    // The color of the cross-clock pixels: 0h, 3h, 6h, 9h
};

void ANIMATION::switchOffRing(void) {
  for (byte i = 0; i < strip.numPixels(); ++i) strip.setPixelColor(i, 0);
  strip.show();
}

// 4 indicators (0h, 3h, 6h, 9h) is flashing every 5 seconds
void ANIMATION::flashLabels(uint16_t S, byte pos) {
 static byte br_lev[] = { 127, 64, 50, 40, 30, 25, 30, 40, 50, 64 };
 const uint16_t tick = 5 * HZ;                      // ticks in 5 seconds

  byte secPart  =  S % tick; 
  byte divider = tick / 10;                         // Normalize time interval to the array size
  byte pos1 = secPart / divider;                    // index in the brightness array 
  byte k = secPart % divider;
  byte pos2 = pos1 + 1; if (pos2 >= 10) pos2 = 0;
  byte br = map(k, 0, divider, br_lev[pos1], br_lev[pos2]);
  int r = int(rgb[0]) * br; r >>= 7;
  int g = int(rgb[1]) * br; g >>= 7;
  int b = int(rgb[3]) * br; b >>= 7;
  uint32_t white = strip.Color(r, g, b);
  int n = strip.numPixels();
  for (byte label = 0; label < n; label += n/4) { 
    if (label == 0 || label != pos) strip.setPixelColor(label, white);
  }
}

uint32_t ANIMATION:: Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color((255 - WheelPos * 3) >> 3, 0, (WheelPos * 3) >> 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, (WheelPos * 3) >> 3, (255 - WheelPos * 3) >> 3);
  }
  WheelPos -= 170;
  return strip.Color((WheelPos * 3) >> 3, (255 - WheelPos * 3) >> 3, 0);
}

void ANIMATION::fadeRing(uint32_t Color, byte shift) {
  byte b = Color;
  byte g = Color >> 8;
  byte r = Color >> 16;
  r >>= shift;
  g >>= shift;
  b >>= shift;
  uint32_t Color_faded = strip.Color(r, g, b);
  for (byte i = 0; i < strip.numPixels(); ++i) strip.setPixelColor(i, Color_faded);
}

void ANIMATION::initXcolor(void) {
  for (byte i = 0; i < 3; ++i)
    rgb[i] = random(32) << 2;
}

//------------------------------ Animation: Fill ring as seconds past, new dot every 2.5 sec -------------------
class FillRing : public ANIMATION {
  public:
    FillRing()                                      { old_pos = 0; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
  private:
    byte old_pos;                                   // Last position filed with new color
};

void FillRing::show(uint16_t S, uint32_t Color) {
  if (S == 0) initXcolor();

  if (S < RingSize) strip.setPixelColor(S, 0);      // Clear the ring in the beginning of the new minutes
  byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);
  if (pos != old_pos) {
    strip.setPixelColor(pos, Color);
    old_pos = pos;
  }
  flashLabels(S, pos);
}

void FillRing::clean(uint16_t S, uint32_t Color) {
  const uint16_t Last8Ticks = 60 * HZ - 8;
  if (S > Last8Ticks) {
    S -= Last8Ticks;
    fadeRing(Color, S);
    flashLabels(S);
  } else {
    show(S, Color);
  }
}

//------------------------------ Animation: Each dot is coming counterclockwize from 12 o'clock ----------------
class cClockRing : public ANIMATION {
  public:
    cClockRing()                                    { run_pos = 255; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
  private:
    byte run_pos;                                   // Last position filed with new color
};

void cClockRing::show(uint16_t S, uint32_t Color) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;

  if (S == 0) initXcolor();

  byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);
  byte runSecond = S % onedot;
  byte newRunPos = RingSize - RingSize * runSecond / onedot;
  if ((newRunPos < RingSize) && newRunPos > pos) {
    if (newRunPos != run_pos) {
      strip.setPixelColor(newRunPos, Color);
      if (run_pos < RingSize) strip.setPixelColor(run_pos, 0);
      run_pos = newRunPos;
    } 
  }
  strip.setPixelColor(pos, Color);
  flashLabels(S, run_pos);
}

void cClockRing::clean(uint16_t S, uint32_t Color) {
 const uint16_t Last8Ticks = 60 * HZ - 8;

  if (S > Last8Ticks) {
    S -= Last8Ticks;
    fadeRing(Color, S);
    flashLabels(S);
  } else {
    show(S, Color);
  }
}

//------------------------------ Animation: Each dot dot is coming counterclockwize from last position ---------
class lpClockRing : public ANIMATION {
  public:
    lpClockRing()                                   { run_pos = 255; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
  private:
    byte run_pos;                                   // Last position filed with new color
};

void lpClockRing::show(uint16_t S, uint32_t Color) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;

  if (S == 0) initXcolor();

  byte pos = uint32_t(S) * RingSize / (uint32_t(HZ) * 60);
  byte runSecond = S % onedot;
  char newRunPos = pos - RingSize * runSecond / onedot;
  if (newRunPos < 0) newRunPos += RingSize; 
  if ((newRunPos < RingSize) && newRunPos != pos) {
    if (newRunPos != run_pos) {
      strip.setPixelColor(newRunPos, Color);
      if (run_pos < RingSize) strip.setPixelColor(run_pos, 0);
      run_pos = newRunPos;
    } 
  }
  strip.setPixelColor(pos, Color);

  flashLabels(S, pos);
}

void lpClockRing::clean(uint16_t S, uint32_t Color) {
 const uint16_t LastTick = 60 * HZ - 1;

  show(S, Color);
  if (S >= LastTick) strip.setPixelColor(RingSize-1, 0);
}

//------------------------------ Animation: Rise the dot slowly ------------------------------------------------
class rsRing : public ANIMATION {
  public:
    rsRing()                                        { keep_pos = true; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
    virtual void  handle(byte k)                    { keep_pos = (k != 0); }
  private:
    bool keep_pos;
};

void rsRing::show(uint16_t S, uint32_t Color) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;

  if (S == 0) initXcolor();
  if (S < RingSize) strip.setPixelColor(S, 0);      // Clear the ring in the beginning of the new minutes
  byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);

  byte b1 = Color;
  byte g1 = Color >> 8;
  byte r1 = Color >> 16;
  byte runSecond = S % onedot;
  byte shift = (runSecond << 3) / onedot;
  r1 >>= (7 - shift);
  g1 >>= (7 - shift);
  b1 >>= (7 - shift);
  strip.setPixelColor(pos, strip.Color(r1, g1, b1));

  if (!keep_pos) {
    byte fadePos = RingSize-1;
    if (pos >= 1) fadePos = pos - 1;
    uint32_t fadeColor = strip.getPixelColor(fadePos);
    b1 = fadeColor;
    g1 = fadeColor >> 8;
    r1 = fadeColor >> 16;
    if (shift > 3) {
      r1 >>= 1;
      g1 >>= 1;
      b1 >>= 1;
    } else if (shift == 7) strip.setPixelColor(fadePos, 0);
    strip.setPixelColor(fadePos, strip.Color(r1, g1, b1));
  }

  flashLabels(S, pos); 
}

void rsRing::clean(uint16_t S, uint32_t Color) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;
 const uint16_t Last8Ticks = 60 * HZ - 8;

  show(S, Color);
  byte runSecond = S % onedot;
  byte shift = (runSecond << 3) / onedot;
  if (S >  Last8Ticks) { 
    if (keep_pos) fadeRing(Color, S);
    if (!keep_pos && (shift == 7)) strip.setPixelColor(RingSize-1, 0);
  }
}

//------------------------------ Animation: Run the sector clockwise -------------------------------------------
class rSectRing : public ANIMATION {
  public:
    rSectRing()                                     { firstPos = 255; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
  private:
    byte firstPos;
};

void rSectRing::show(uint16_t S, uint32_t Color) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;

  if (S == 0) initXcolor();

  byte length = (uint32_t)S * RingSize / ((uint32_t)HZ * 60) + 1;
  byte runSecond = S % onedot;
  byte newFirstPos = RingSize * runSecond / onedot;
  char lastPos = newFirstPos - length;
  if (lastPos < 0) lastPos += RingSize;
  if (newFirstPos < RingSize) {
    if (newFirstPos != firstPos) {
      strip.setPixelColor(newFirstPos, Color);
      if (lastPos >= 0) strip.setPixelColor(lastPos, 0);
      firstPos = newFirstPos;
    } 
  }
  flashLabels(S, firstPos);
}

void rSectRing::clean(uint16_t S, uint32_t Color) {
 const uint16_t Last8Ticks = 60 * HZ - 8;

  if (S == 0) initXcolor();

  if (S > Last8Ticks) {
    S -= Last8Ticks;
    fadeRing(Color, S);
    flashLabels(S);
  } else {
    show(S, Color);
  }
}

//------------------------------ Animation: Swing the sector ---------------------------------------------------
class swingRing : public ANIMATION {
  public:
    swingRing()                                     { firstPos = 255; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
  private:
    byte firstPos;
};

void swingRing::show(uint16_t S, uint32_t Color) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;

  if (S == 0) initXcolor();

  byte length = (uint32_t)S * RingSize / ((uint32_t)HZ * 60) + 1;
  byte runSecond = S % onedot;
  byte newFirstPos;
  char lastPos;
  if ((length % 2) == 0) {                          // even
    newFirstPos = RingSize - RingSize * runSecond / onedot;
    lastPos = newFirstPos + length;
    if (lastPos >= RingSize) lastPos = -1;
  } else {                                          // odd
    newFirstPos = RingSize * runSecond / onedot;
    lastPos = newFirstPos - length;
    if (lastPos < 0) lastPos = -1;
  }
  if (newFirstPos < RingSize) {
    if (newFirstPos != firstPos) {
      strip.setPixelColor(newFirstPos, Color);
      if ((lastPos >= 0) && (lastPos < RingSize))
        strip.setPixelColor(lastPos, 0);
      firstPos = newFirstPos;
    } 
  }
  flashLabels(S, firstPos);
}

void swingRing::clean(uint16_t S, uint32_t Color) {
 const uint16_t Last8Ticks = 60 * HZ - 8;

  if (S > Last8Ticks) {
    S -= Last8Ticks;
    fadeRing(Color, S);
    flashLabels(S);
  } else {
    show(S, Color);
  }
}

//------------------------------ Animation: Fill ring wth the rainbow as seconds past --------------------------
class fRainRing : public ANIMATION {
  public:
    fRainRing()                                     { firstPos = 255; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
  private:
    byte firstPos;
};

void fRainRing::show(uint16_t S, uint32_t Color) {
  if (S == 0) initXcolor();
  
  if (S < RingSize) strip.setPixelColor(S, 0);      // Clear the ring in the beginning of the new minutes
  for(uint16_t i = 1; i <= RingSize; ++i) {
    strip.setPixelColor(RingSize - i, Wheel(i+S));
  }
  flashLabels(S);
}

void fRainRing::clean(uint16_t S, uint32_t Color) {
 const uint16_t LastRSTicks = 60 * HZ - RingSize;

  if (S > LastRSTicks) {
    S -= LastRSTicks;
    strip.setPixelColor(S, 0);
  } else {
    show(S, Color);
  }
}

//------------------------------ Animation: Fill ring wth the another rainbow as seconds past ------------------
class aRainRing : public ANIMATION {
  public:
    aRainRing()                                     { firstPos = 255; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
  private:
    byte firstPos;
};

void aRainRing::show(uint16_t S, uint32_t Color) {
  if (S == 0) initXcolor();

  if (S < RingSize) strip.setPixelColor(S, 0);      // Clear the ring in the beginning of the new minutes
  for(uint16_t i = 1; i <= RingSize; ++i) {
    strip.setPixelColor(RingSize - i, Wheel(((i * 256 / RingSize) + S) & 255));
  }
  flashLabels(S);
}

void aRainRing::clean(uint16_t S, uint32_t Color) {
 const uint16_t LastRSTicks = 60 * HZ - RingSize;

  if (S > LastRSTicks) {
    S -= LastRSTicks;
    strip.setPixelColor(S, 0);
  } else {
    show(S, Color);
  }
}

//------------------------------ Animation: Wake-up signal, sunrise --------------------------------------------
class sunRise : public ANIMATION {
  public:
    sunRise()                                       { loop_number = 0; started = false; for (byte i = 0; i < 3; rgb[i++] = 0); }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
    virtual void  handle(byte k)                    { loop_number = 0; started = false; for (byte i = 0; i < 3; rgb[i++] = 0); }
  private:
   uint16_t   loop_number;                          // [0 - 4]
   bool       started;                              // Whether the sequence started correctly
   const byte rise[6][3] = {{0, 0, 0}, {33, 2, 0}, {66, 6, 2}, {99, 18, 3}, {133, 24, 4}, {255, 50, 50}};
  
};

void sunRise::show(uint16_t S, uint32_t Color) {
  if (!started) {
    if  (S > 300) return;                           // Do not start the sequence at the end of minute
    started = true;
  }
  byte rb = map(uint32_t(loop_number) * 600 + S, 0, 1800, 1, RingSize/2);
  rb = constrain(rb, 1, RingSize/2);
  byte lb = constrain(RingSize-rb, RingSize/2+1, RingSize-1);

  for (byte i = 0; i < 3; ++i)
    rgb[i] = map(S, 0, 600, rise[loop_number][i], rise[loop_number+1][i]);

  uint32_t c = strip.Color(rgb[0], rgb[1], rgb[2]); 
  for (byte i = RingSize-1; i >= lb; --i) strip.setPixelColor(i, c);
  for (byte i = 0; i <= rb; ++i) strip.setPixelColor(i, c);
  if (S == 599) {
    if (loop_number < 4) ++loop_number;
  }
}

void sunRise::clean(uint16_t S, uint32_t Color) {
 const uint16_t LastRSTicks = 60 * HZ - RingSize;

  if (S > LastRSTicks) {
    S -= LastRSTicks;
    strip.setPixelColor(S, 0);
  } else {
    show(S, Color);
  }
}


//------------------------------ Animation: night light, fireplace ---------------------------------------------
class firePlace : public ANIMATION {
  public:
    firePlace()                                     { factor = 1; indx = 0; }
    virtual void  show(uint16_t S,  uint32_t Color);
    virtual void  clean(uint16_t S, uint32_t Color);
    virtual void  handle(byte k)                    { factor = constrain(k, 1, RingSize/2); indx = factor - 1; }
  private:
    void      addColor(uint8_t position, uint32_t color);
    void      substractColor(uint8_t position, uint32_t color);
    uint32_t  blend(uint32_t color1, uint32_t color2);
    uint32_t  substract(uint32_t color1, uint32_t color2);
    int       factor;                               // Only pixels divided by the factor are lit
    int       indx;                                 // Index of the first pixel
    const uint32_t fire_color = 0x050200;
};

void firePlace::show(uint16_t S, uint32_t Color) {
  if ((S == 599) && (--indx < 0)) indx = factor-1;  // shift lit pixels every minute
  if (S % random(1, 4)) return;
  for (byte i = 0; i < RingSize; ++i) {
    strip.setPixelColor(i, 0);
    if (((i + indx) % factor) == 0) {               // Tune the brightness by the factor value
      addColor(i, fire_color);
      byte r = random(3);
      uint32_t diff_color = strip.Color(r, r/2, r/2);
      substractColor(i, diff_color);
    }
  }  
}

void firePlace::clean(uint16_t S, uint32_t Color) {
 const uint16_t LastRSTicks = 60 * HZ - RingSize;
  if (S > LastRSTicks) {
    S -= LastRSTicks;
    strip.setPixelColor(S, 0);
  } else {
    show(S, Color);
  }
}

void firePlace::addColor(uint8_t position, uint32_t color) {
  uint32_t blended_color = blend(strip.getPixelColor(position), color);
  strip.setPixelColor(position, blended_color);
}

void firePlace::substractColor(uint8_t position, uint32_t color) {
  uint32_t blended_color = substract(strip.getPixelColor(position), color);
  strip.setPixelColor(position, blended_color);
}

uint32_t firePlace::blend(uint32_t color1, uint32_t color2) {
  byte r1,g1,b1;
  byte r2,g2,b2;

  r1 = (byte)(color1 >> 16),
  g1 = (byte)(color1 >>  8),
  b1 = (byte)(color1 >>  0);

  r2 = (byte)(color2 >> 16),
  g2 = (byte)(color2 >>  8),
  b2 = (byte)(color2 >>  0);
  return strip.Color(constrain(r1+r2, 0, 255), constrain(g1+g2, 0, 255), constrain(b1+b2, 0, 255));
}

uint32_t firePlace::substract(uint32_t color1, uint32_t color2) {
  byte r1,g1,b1;
  byte r2,g2,b2;
  int16_t r,g,b;

  r1 = (byte)(color1 >> 16),
  g1 = (byte)(color1 >>  8),
  b1 = (byte)(color1 >>  0);

  r2 = (byte)(color2 >> 16),
  g2 = (byte)(color2 >>  8),
  b2 = (byte)(color2 >>  0);

  r = (int16_t)r1 - (int16_t)r2;
  g = (int16_t)g1 - (int16_t)g2;
  b = (int16_t)b1 - (int16_t)b2;

  if(r < 0) r = 0;
  if(g < 0) g = 0;
  if(b < 0) b = 0;
  return strip.Color(r, g, b);
}

//------------------------------------------ class ALARM -------------------------------------------------------
class ALARM {
  public:
    ALARM()                                         { }
    void      init(bool act, uint16_t minutes = 0, byte wmask = 0);
    void      activate(bool a)                      { if ((active = a)) calculateAlarmTime(); }
    bool      isActive(void)                        { return active; }
    byte      wdayMask(void)                        { return wday_mask; }
    void      calculateAlarmTime(bool next = false);// Calculate the next alarm time; in next, calculate the next alarm time in one minute from the current time
    bool      isAlarmNow(void);                     // Whether the alarm should be started
    void      stopAlarm(void)                       { firing = false; }
    time_t    alarmTime(void)                       { return next_alarm; }
  private:
    time_t    next_alarm;                           // The UNIX time of the next alarm
    time_t    stop_time;                            // The time when to stop active alarm
    uint16_t  alarm_time;                           // The minutes from midnight to fire the alarm
    byte      wday_mask;                            // The week day bitmask to fire the alarm 0 - sunday, 7 saturday
    bool      active;                               // Whether the alarm is active
    bool      firing;                               // Whether tha alarm is firing right now
};

void ALARM::init(bool act, uint16_t minutes, byte wmask) {
  active = act;
  if (minutes >= 24*60) minutes = 24*60-1;
  alarm_time = minutes;
  wday_mask  = wmask & 0b01111111;
  if (active) calculateAlarmTime();
}

bool ALARM::isAlarmNow(void) {
  time_t n = now();
  if (firing) {
    if (n >= stop_time) {                           // The time to stop alarm
      firing = false;
      stop_time = 0;
    }
    return firing;
  }
  if (!active) return firing;                       // There is no active alarm

  byte S = n % 60;
  if (!firing && (S <= 10) && (n - next_alarm) <= 10) {
    firing = true;                                // It is Time to fire the alarm
    stop_time = next_alarm + 300;                 // Set the time to stop alarm firing (5 minutes)
    calculateAlarmTime(true);
  }
    
  return firing;
}

void ALARM::calculateAlarmTime(bool next) {
  tmElements_t tm;
  next_alarm = 0;
  if (!active) return;

  time_t n = RTC.get();                             // RTC.read load weekday incorectly
  if (next) n += 60;                                // Calculate next alarm in one minute of the current time
  breakTime(n, tm);
  tm.Second = 0;
  long dm = long(tm.Hour) * 60 + tm.Minute;         // The time in minutes since midnight

  long delta_minutes = long(alarm_time) - dm;
  byte delta_days = 0;
  if (wday_mask) {                                  // This alarm should be repeated some week days
    byte m = 1;
    delta_days = 8;
    for (byte j = 0; j < 7; ++j) {
      if (wday_mask & m) {                          // Fire the alarm in j - day 
        char d = j + 1 - tm.Wday;                   // sunday is 1
        if (d < 0) d += 7;
        if ((d == 0) && (delta_minutes <= 0)) d = 7;  // Today is late for this alarm, we should fire it in a week
...

This file has been truncated, please download it to see its full contents.

Credits

Alexander

Alexander

31 projects • 298 followers
Where is John Galt?

Comments