//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.
Comments