//The clock with 4x7-segment indicator with a shift register and neopixel rgb 24 led ring @ arduino nano 16 MHz
#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) {
b_pin = ButtonPIN;
bpt = 0;
over_press = timeout_ms;
tick_period = 0;
tick_time = 0;
i_b_rel = false;
b_on = false;
b_check = 0;
}
void init(void) { pinMode(b_pin, INPUT_PULLUP); }
void setTimeout(uint16_t timeout_ms = 3000) { over_press = timeout_ms; tick_period = 0; }
void setTick(uint16_t to);
bool buttonTick(void);
byte buttonStatus(void);
private:
int32_t expAverage(int32_t value);
volatile uint32_t button_data; // Exponential average value of button port (to debounce)
uint16_t over_press; // Maxumum time in ms the button can be pressed
uint16_t tick_period; // Repeat 'tick' period
uint32_t tick_time; // Time in ms when the last 'tick' was generated
volatile uint32_t bpt; // Time when the button was pressed (ms)
byte b_pin; // The pin number connected to the button
bool i_b_rel; // Ignore button release event
bool b_on; // The button current position: true - pressed
uint32_t b_check; // Time when the button should be checked (ms)
const byte b_bounce = 50; // Bouncing timeout (ms)
const uint16_t b_def_over_press = 3000; // Default value for button overpress timeout (ms)
const byte b_trigger_on = 100; // average limit to change button status to on
const byte b_trigger_off = 50; // average limit to change button status to off
const byte b_avg_length = 4; // exponential average length
const byte b_check_period = 20; // The button check period, ms
const uint16_t b_long_press = 1500; // If the button was pressed more that this timeout, we assume the long button press
};
void BUTTON::setTick(uint16_t to) {
if (to > 0) { // Setup tick period
tick_period = constrain(to, 100, 500);
over_press = 16000;
return true;
} else { // disable tick
tick_period = 0;
over_press = b_def_over_press;
}
return false;
}
bool BUTTON::buttonTick(void) { // When the button pressed for a while, generate periodical ticks
uint32_t now_t = millis();
if (now_t - tick_time > tick_period) {
tick_time = now_t;
return (bpt != 0);
}
return false;
}
/*
* The Encoder button current status
* 0 - not pressed
* 1 - short press
* 2 - long press
*/
byte BUTTON::buttonStatus(void) {
if (millis() >= b_check) { // It is time to check the button status
b_check = millis() + b_check_period;
byte s = 0;
if (0 == digitalRead(b_pin)) // if port state is low, the button pressed
s = b_trigger_on << 1;
if (b_on) {
if (expAverage(s) < b_trigger_off)
b_on = false;
} else {
if (expAverage(s) > b_trigger_on)
b_on = true;
}
if (b_on) { // Button status is 'pressed'
uint32_t n = millis() - bpt;
if ((bpt == 0) || (n > over_press)) {
bpt = millis();
} else if (n > b_long_press) { // Long press
if (i_b_rel) {
return 0;
} else {
if (tick_period)
return buttonTick();
i_b_rel = true;
return 2;
}
}
} else { // Button status is 'not pressed'
if (bpt == 0 || i_b_rel) {
bpt = 0;
i_b_rel = false;
return 0;
}
uint32_t e = millis() - bpt;
bpt = 0; // Ready for next press
if (e < over_press) { // Long press already managed
return 1;
}
}
}
return 0;
}
int32_t BUTTON::expAverage(int32_t value) {
byte round_v = b_avg_length >> 1;
button_data += value - (button_data + round_v) / b_avg_length;
return (button_data + round_v) / b_avg_length;
}
//------------------------------------------ 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 };
const byte s_dot = 1;
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();
symbol[3] = hex[10] | s_dot; // 'A.'
break;
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();
}
...
This file has been truncated, please download it to see its full contents.
Comments