/*
==== DOKUMENTATION ====================================================
=== GENERELLE INFOS ===
die Zeit hat einen (relativ großen) Drift und es ist nötig sie immer wieder neu einzustellen, das sie nicht zu sehr abweicht
nach Stromausfall sind alle Einstellungen gelöscht -> könnte man Softwaretechnisch beheben mit RTC-variablen oder Flash
Stromverbrauch relativ gering, Betrieb mit Powerbank sicherlich möglich
=== drei knöpfe von links nach rechts ===
Abbrechen/Zurück
Ok/Erhöhung
Weiter
- Abbrechen -
im Normalen Modus -> in den Schlaf
in den Einstellungen -> eine Ebene hoch
in der Bearbeitung -> zum URPSRUNGSWERT zurückgesetzt
- Ok/Erhöhung -
im Normalen Modus -> in die Einstellungen
in den Einstellungen -> tiefer gehen / Ebene runter
in der Bearbeitung -> Wert hoch, wenn Wert über Maximum dann geht er wieder auf 0
- Weiter -
im Normalen Modus -> Licht an/aus
in den Einstellungen -> Einstellung weiter gehen
in der Bearbeitung -> Bestätigen
=== Einstellungen ===
- allgemein -
Smileys zeigen ein Ja (fröhlich) oder Nein (traurig)
chronologisch
1. Alarm einstellen
Alarm pro wochentag einstellbar
Alarm kann allgemein deaktiviert werden und die Alarmzeiten werden gespeichert
Langer Klick vom zweiten Button -> Menü öffnet um neue Alarmzeit hinzuzufügen oder zu löschen
2. Alarm Termin
Alarm an bestimmten Tag anders machen -> überschreibt den "Wochenalarm"
edit zeigt alle Alarme -> mit Ok und dann nochmal bestätigen kann man löschen
3. Minispiel
Snake -> unten erklärt, klassisches Snake
Pi -> Nachkommestellen von Pi anzeigen
4. LED
Custom LED farbe einstellen
Custom LED als standard einstellen -> geht an wenn man die led normal anmacht
Custom LED als besondere Farbe abspeichern -> kann man so anmachen
rainbow -> rainbow
5. Musik
Lautstärkeneinstellung
Musik Auswahl -> Musik kann aktiviert werden, Musik wird zufällig ausgewählt
Custom Musik Plätze -> Auf die SD-Karte können extra Musik geladen werden -> NAMENSCHEMA BEACHTEN(z.B.0033_abcdefg, alles nach dem _ wird ignoriert, die Zahl ist wichtig)
Qualität schlecht -> Deckel auf ist besser, zur Not neuen Lautsprecher einlöten, einfach +-, bzw. Aux wenn man wollte
6. Sonnenaufgang
Alarm dauer einstellbar
Led helligkeit einstellbar
einen Sonnenaufgang auswählen
wenn Custom gewollt ist -> einstellbar in der Liste "sunsets" (RGB) -> neukompilen bei "NACHMACHEN" erklärt
7. generelle Einstellungen
Datum, Zeit, Bildschirmhelligkeit, Zeit bis zum Einschlafen einstellbar
Offset für die Zeit einstellen (Einheit ist Millisekunden/Tag und dann wird das entweder draufgerechnet ("+") oder abgezogen ("-")
Einstellungen lassen sich abspeichern und wiedernutzen -> braucht flash cyclen, also nicht zu sehr benutzen
======
- Minispiel "Snake" -
bewegen mit Ok für Drehung gegen den- und Erhöhung für mit dem Urzeigersinn
Ziel -> Äpfel essen
Wände sind tötlich
Highscrore am Ende
- Schlaf -
schläft nach gewisser Zeit, einstellbar bei "sleeptime" unter den generellen Einstellungen
wächt vor alarm und bei knopfdruck auf, auch manchmal mitternachts, aber dann unbemerkt
- Alarm -
Lichtalarm und Ton, beides konfigurierbar
stoppt bei beliebiger Taste
KEIN SNOOZE
Einstellmöglichkeiten bei "- Einstellungen -"
==== BESSER MACHEN ====================================================
software:
- printdings mit autovariablen -> autovariablen fixen
- funktion-verwirrung
- vllt lange klicks ausnutzen, bzw. doppelklicks -> weniger klick-chaos
- man kann den printinterval wegmachen und einfach neu rechnen, wenn sich die Zeit verändert hat
- Zeitumstellung programmieren
hardware:
- buttons auf RTC pins löten und deepsleep nutzen
- power LED ablöten (energiesparen + nervig)
- bessere Audioqualität
- bessere Sonnenaufgänge
- haptisches Feedback (immer cool)
==== NACHBAUEN ========================================================
software:
- Kompilieren mit z.B. Arduino IDE
- #define NORMAL_MODE muss einkommentiert sein
- Board bei Arduino muss ESP32 DEV MODULE (braucht glaub eine Erweiterung)
- Bibliotheken werden benötigt
-> wenn auf Wokwi, dann #define NORMAL_MODE auskommentieren und diagram.json ganz am Ende des Codes nehmen + libraries.txt einfügen
hardware:
- Buttons sind nach INPUT geschalten nicht INPUT_PULLUP
- MAX7219-LED-Matrix (32x8)
- DFPlayer Mini für den Ton
- Adafruit Led Ring ist hell genug -> vllt sogar was dunkleres
- Manches braucht 5V!, manches aber 3.3V, aufpassen!
*/
//NORMAL_MODE = not Wokwi simulation -> has sound system
//#define NORMAL_MODE
//ALARM_TEST = button 3 activates alarm instead of light -> testing
//#define ALARM_TEST
//user specific flag, e.g. pin configuration is different, no rtc
#define IS_FIDO
#define IS_PULLUP
//should use eastereggs
#define USE_EASTEREGG
#include <vector>
#include <array>
uint32_t low32 = 0;
uint32_t high32 = 0;
//overflows at 500m years, normal millis() overflows way too soon
uint64_t millis64(){
uint32_t new_low32 = millis();
if (new_low32 < low32) high32++;
low32 = new_low32;
return (uint64_t) high32 << 32 | low32;
}
//ChatGPT-Implementierung vom Doomsday-Algorithmus, scheint zu stimmen (if it works dont touch it)
int get_week_day(int day, int month, int year) {
// 1. Jahrhundertanker (für Gregorianischen Kalender)
int century = year / 100;
int anchor;
switch (century % 4) {
case 0: anchor = 2; break; // 1600, 2000 -> Dienstag
case 1: anchor = 0; break; // 1700, 2100 -> Sonntag
case 2: anchor = 5; break; // 1800, 2200 -> Freitag
case 3: anchor = 3; break; // 1900, 2300 -> Mittwoch
}
// 2. Letzte zwei Ziffern vom Jahr
int y = year % 100;
int doomsday = (y/12 + (y%12) + (y%12)/4 + anchor) % 7;
// 3. Doomsdays pro Monat
int month_doomsday[] = {0, 3, 28, 14, 4, 9, 6, 11, 8, 5, 10, 7, 12};
if (month == 1) month_doomsday[1] = ( (year%4==0 && (year%100!=0 || year%400==0)) ? 4 : 3 );
if (month == 2) month_doomsday[2] = ( (year%4==0 && (year%100!=0 || year%400==0)) ? 29 : 28 );
// 4. Differenz berechnen
int weekday = (doomsday + (day - month_doomsday[month])) % 7;
if (weekday < 0) weekday += 7;
return weekday-1<0?6:weekday-1; // 1=Montag, ...
}
#ifdef NORMAL_MODE
#include "SoftwareSerial.h"
//MUSIC----------------------------
#include "DFRobotDFPlayerMini.h"
SoftwareSerial software_serial_music(16,17);//rx;tx
DFRobotDFPlayerMini music_player;
#endif
//FLASH SAVE ----------------------
#include <Preferences.h>
Preferences prefs;
struct packed{
uint64_t data = 0;
uint8_t idx = 0;
};
void pack(struct packed& p, const uint64_t data, const uint8_t bits){
p.data |= (data << p.idx);
p.idx += bits;
}
uint64_t unpack(uint64_t& p, const uint8_t bits){
uint64_t tmp = p & ((1ULL << bits)-1); //bit-magic
p = p >> bits;
return tmp;
}
//DISPLAY--------------------------
#include <U8g2lib.h>
U8G2_MAX7219_32X8_F_4W_SW_SPI u8g2(U8G2_R0, /*clock*/4, /*data*/15, /*cs*/2, U8X8_PIN_NONE, U8X8_PIN_NONE);
#define b1 5 //pwm isnt needed
#define b2 18 //''
#define b3 19 //''
#define rebounce_delay 100
bool bs1 = 0;bool bs2 = 0;bool bs3 = 0;
#include <Adafruit_NeoPixel.h>
#define PIN 21
Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN, NEO_GRB + NEO_KHZ800);
//sets led ring with r,g,b and a number of pixels depending on brightness element [0,1.0f]
void display_color(uint8_t r, uint8_t g, uint8_t b, float brightness){
uint32_t c = strip.Color(r,g,b);
uint8_t n = brightness*11+1;
for(uint8_t i = 0; i < 12; i++) strip.setPixelColor(i, strip.Color(0,0,0));
for(uint8_t i = 0; i < n; ++i){
strip.setPixelColor((uint8_t)((float)i/(float)n*12), strip.gamma32(c));
}
strip.show();
}
//turn led ring off
void display_nothing(){
for(uint8_t i = 0; i < 12; i++) strip.setPixelColor(i, strip.Color(0,0,0));
strip.show();
}
//set backups, and mth and year
uint8_t m;uint8_t backupm = 33;
uint8_t h;uint8_t backuph = 11;
uint8_t day;uint8_t backupday = 1;
uint8_t mth = 1;
uint64_t passedmth = 0;
uint8_t year = 25;
uint64_t time_offset = 2000;
bool time_offset_dir = false; //true = +; false = -
uint8_t monthsz[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};//month lengths, idx is the normal month so monthsz[0] is undefined
//try to counteract time drift
inline uint64_t calc_offset(uint64_t timespan){
return timespan*(1000*60*60*24+time_offset*(time_offset_dir?1:-1))/(1000*60*60*24);
}
//some code for time thing, dont touch, future self: pls just use a library, very unnecessary, maybe implement summer/winter time
uint64_t lastrefreshed = 0;
void calc_time(){
uint64_t millis_since_last = calc_offset(millis64() - lastrefreshed);
m = (backupm + (uint64_t)(millis_since_last/60000))%60;
h = (backuph + (uint64_t)(millis_since_last/3600000) + (m < backupm))%24;
day = (backupday - 1 + (uint64_t)(millis_since_last/86400000) + (h < backuph ? 1 : (h == backuph && m < backupm)) - passedmth)%monthsz[mth] + 1;
if(day == 1 && (backupday - 1 + (uint64_t)(millis_since_last/86400000) + (h < backuph ? 1 : (h == backuph && m < backupm))) >= monthsz[mth]){
passedmth += monthsz[mth];
++mth;
if(mth > 12){
mth = 1;
++year;
monthsz[2] = (year % 4 == 0 ? 29 : 28);
}
}
}
uint8_t counter[2] = {0,0};//2 levels
bool lighton = false;
bool sleeping = false;
#define options_sz 7
//bitmaps for the settings icons
const unsigned char options[options_sz][8] = {
{B00111000,
B01000100,
B10010010,
B10011010,
B10000010,
B01000100,
B00111000,
B00000000},
{B00011000,
B00011000,
B00011000,
B00011000,
B00000000,
B00011000,
B00011000,
B00000000},
{B00000000,
B00100100,
B01011010,
B10000001,
B10011001,
B10100101,
B01000010,
B00000000},
{B00000000,
B00011100,
B00100010,
B00100010,
B00010100,
B00010100,
B00001000,
B00000000},
{B00011000,
B00011100,
B00010100,
B00010000,
B00010000,
B01110000,
B00110000,
B00000000},
{B00000000,
B00000000,
B00000000,
B00011000,
B00100100,
B00100100,
B01111110,
B00000000},
{B00011000,
B00100100,
B01000000,
B01000000,
B01001110,
B00100110,
B00011010,
B00000000}
};
//bitmaps for smileys
const unsigned char icons[2][4] = {
{B01010000,
B00000000,
B10010000,
B01100000},
{B01010000,
B00000000,
B01100000,
B10010000}
};
//bitmaps for alarm selection
const unsigned char alarm_edit_bitmap[3][8] = {
{B00000000,
B00010000,
B00010000,
B01111100,
B00010000,
B00010000,
B00000000,
B00000000},
{B00000000,
B00000000,
B00000000,
B01111100,
B00000000,
B00000000,
B00000000,
B00000000},
{B00000000,
B00010000,
B00100000,
B01111110,
B00100000,
B00010000,
B00000000,
B00000000}
};
#define songs_sz 43
bool songs_active[songs_sz] = {0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
String songs_name[songs_sz] = {"1_alarm","2_alarm","crystals_alarm","iphone_alarm","silk_alarm","fire_ambient","forest1_ambient","forest2_ambient","forest3_ambient","forest4_ambient","seawaves1_ambient","seawaves2_ambient","seawaves3_ambient","seawaves4_ambient","seawaves5_ambient","seawaves6_ambient","farm1","farm2","farm3","loud_rap","mexico_rap","milliondollarbaby_rap","n95_rap","paintthetownred_rap","praisegod_rap","sprinter_rap","superman_rap","tillicollapse_rap","withoutme_rap","aufstehn_song","jazzinparis_song","moonlight_song","zapmamacitoyen_song","zapmamaquestce_song","custom1","custom2","custom3","custom4","custom5","custom6","custom7","custom8","custom9"};
#define alarms_sz 3
String weekdays[7] = {"mon","tue","wed","thu","fri","sat","sun"};
uint8_t alarm_time[7][alarms_sz][2] = {{{6,0},{7,30},{25,0}},{{6,0},{25,0},{25,0}},{{6,0},{25,0},{25,0}},{{6,0},{25,0},{25,0}},{{6,0},{25,0},{25,0}},{{24,0},{25,0},{25,0}},{{24,0},{25,0},{25,0}}};
bool no_alarm = false;
uint8_t activated_alarm_for_day;
uint8_t next_alarm[2] = {7,0};
String relative_times[3] = {"heute","morgen","uebermorgen"};
std::vector<std::array<uint8_t,5>> appointments = {}; //hh:mm / dd:mm:yyyy
#define sunsets_sz 7
#define sunset_length 9
String sunset_name[sunsets_sz] = {"fast", "slow", "minecrafty", "golden", "warm", "simple", "nolight"};
uint8_t sunsets[sunsets_sz][sunset_length][3] = {{
{0 , 0 , 0},
{20 , 5 , 0},
{40 , 10 , 0},
{80 , 30 , 10},
{120, 60 , 20},
{180, 100, 50},
{255, 160, 100},
{255, 200, 150},
{255, 240, 220}},
{{50 , 47 , 37},
{100, 95 , 75},
{150, 140, 110},
{200, 180, 140},
{230, 200, 155},
{255, 220, 175},
{255, 180, 160},
{230, 140, 155},
{190, 110, 140}},
{{0 , 0 , 0 },
{15 , 5 , 30 },
{50 , 30 , 70 },
{100, 50 , 120},
{160, 100, 150},
{220, 140, 100},
{255, 180, 80 },
{255, 210, 140},
{255, 235, 200}},
{{10 , 10 , 0 },
{60 , 30 , 5 },
{120, 70 , 10 },
{200, 120, 40 },
{255, 170, 80 },
{255, 200, 130},
{255, 220, 170},
{255, 240, 200},
{255, 255, 230}},
{{0 , 0 , 20 },
{30 , 20 , 80 },
{70 , 50 , 140},
{130, 70 , 180},
{200, 120, 150},
{230, 160, 100},
{255, 200, 80 },
{255, 220, 150},
{255, 245, 220}},
{{30 , 0 , 0 },
{70 , 10 , 0 },
{110, 30 , 5 },
{150, 55 , 15 },
{190, 85 , 30 },
{220, 120, 60 },
{240, 160, 100},
{250, 200, 150},
{255, 240, 220}},
{{0 , 0 , 0 },
{0 , 0 , 0 },
{0 , 0 , 0 },
{0 , 0 , 0 },
{0 , 0 , 0 },
{0 , 0 , 0 },
{0 , 0 , 0 },
{0 , 0 , 0 },
{0 , 0 , 0 }}
};
uint8_t active_sunset = 3;
#define sunset_brightness_max 255
#ifdef NORMAL_MODE
uint8_t sunset_brightness = 20;
#else
uint8_t sunset_brightness = 255;
#endif
#define sunset_speed_max 89 //1-59s, 1-30min
uint8_t sunset_speed = 20;
#define snooze_time_max 89 //1-59s, 1-30min
uint8_t snooze_time = 64;
#define display_brightness_max 255
uint8_t display_brightness = 10;
String ledoptions[7] = {"custom","setcolor","standard","color1","color2","color3","rainbow"};
uint8_t ledcolor[4][3] = {{252,238,167},{235,64,52},{30,133,201},{118,17,186}};
uint8_t tmpcolor[3] = {252,238,167};
#define volume_max 30
uint8_t volume = 16;
#define sound_offset_max 100 //100%
uint8_t sound_offset = 0;
#define sleeptime_max 89 //1-59s, 1-30min
bool sleepb = true;
uint8_t sleeptime = 60;
uint64_t lastsleep = 0;
void calc_next_calendar_day(uint8_t& d, uint8_t& m, uint8_t& y){
++d;
uint8_t monthsz_ = monthsz[m];
if(m == 2 && y % 4 != 0) monthsz_ = 29;
else if(m == 2) monthsz_ = 28;
if(d > monthsz_){
d = 1;
++m;
if(m > 12){
m = 1;
++y;
}
}
}
uint8_t calced_alarm_for_day = 32;
void calc_next_alarm(){
bool app = false;
for(uint8_t i = 0; i < appointments.size(); ++i) if(appointments[i][2] == day && appointments[i][3] == mth && appointments[i][4] == year){
next_alarm[0] = appointments[i][0];
next_alarm[1] = appointments[i][1];
app = true;
}
if(!app){
int wd = get_week_day(day,mth,year);
uint8_t best = 0;
for(uint8_t i = 1; i < alarms_sz; ++i){
bool best_not_good_at_all = alarm_time[wd][best][0] > 23 || alarm_time[wd][best][0] < h || ((alarm_time[wd][best][0] == h)&&(alarm_time[wd][best][1] < m));
bool new_not_good_at_all = alarm_time[wd][i][0] > 23 || alarm_time[wd][i][0] < h || ((alarm_time[wd][i][0] == h)&&(alarm_time[wd][i][1] < m));
bool best_not_good_enough = alarm_time[wd][i][0] < alarm_time[wd][best][0] || ((alarm_time[wd][i][0] == alarm_time[wd][best][0])&&(alarm_time[wd][i][1] < alarm_time[wd][best][1]));
if(!new_not_good_at_all && (best_not_good_at_all || best_not_good_enough)) best = i;
}
next_alarm[0] = no_alarm?24:(alarm_time[wd][best][0]>24?24:alarm_time[wd][best][0]);//alarm not existant == no alarm
next_alarm[1] = alarm_time[wd][best][1];
}
if(h < next_alarm[0] || (h == next_alarm[0] && m < next_alarm[1]))
activated_alarm_for_day = 32; //impossible
else
activated_alarm_for_day = day;
calced_alarm_for_day = day;
}
void print_selection(){
u8g2.clearBuffer();
for(uint8_t i = 0; i < 4; ++i){
if((uint8_t)(counter[0]/4)*4 + i > options_sz - 1) break;
u8g2.drawBitmap(i*8, 0, 8/8, 7, options[(uint8_t)(counter[0]/4)*4 + i]);
}
u8g2.drawLine((counter[0]%4)*8,7,(counter[0]%4)*8+7,7);
u8g2.sendBuffer();
}
void print_selection_alarm(uint8_t idx){
u8g2.clearBuffer();
for(uint8_t i = 0; i < 3; ++i){
u8g2.drawBitmap(i*8, 0, 8/8, 7, alarm_edit_bitmap[(uint8_t)(counter[0]/4)*4 + i]);
}
u8g2.drawLine(idx*8,7,idx*8+7,7);
u8g2.sendBuffer();
}
void print_time(uint8_t h_, uint8_t m_, uint8_t hightlight = 2){
u8g2.clearBuffer();
u8g2.setCursor((h_>9?15-8:15-4), 7);
u8g2.print((String(h_) + ":" + (m_<10?"0":"") + String(m_)).c_str());
if(hightlight == 0) u8g2.drawLine((h_>9?15-8:15-4),7,13,7);
else if(hightlight == 1) u8g2.drawLine(17,7,23,7);
u8g2.sendBuffer();
}
#define waittime 1000
#define scrollspeed 15
#define worddist 6
String name_save = "";
int b_save = 0;
int32_t scrolldist = 0;
uint64_t start_time = 0;
int32_t position = 0;
void print_option_update();
void print_option(String name, bool b, bool checkmark = true){
scrolldist = u8g2.getStrWidth(name.c_str()) + worddist;
name_save = name;
start_time = millis64();
b_save = (checkmark?(b?1:0):2);
print_option_update();
}
void print_option_update(){
u8g2.clearBuffer();
if(scrolldist - worddist +1+(b_save==2 ? 0 : 5) <= 32){
u8g2.setCursor(1, 7);
u8g2.print(name_save.c_str());
}
else if((millis64()-start_time)%(waittime+1000*scrolldist/scrollspeed) <= waittime){
position = 1;
for(; position < 32; position += scrolldist){
u8g2.setCursor(position, 7);
u8g2.print(name_save.c_str());
}
}
else{
position = 1-((((millis64()-start_time)%(waittime+1000*scrolldist/scrollspeed))-waittime))*scrollspeed/1000;
for(; position < 32; position += scrolldist){
u8g2.setCursor(position, 7);
u8g2.print(name_save.c_str());
}
}
if(b_save!=2){
u8g2.setDrawColor(0);
u8g2.drawBox(27,0,5,8);
u8g2.setDrawColor(1);
u8g2.drawBitmap(28,2,1,4,icons[b_save==1?0:1]);
}
u8g2.sendBuffer();
}
#define fastwaittime 700
uint64_t lastfastb3 = 0;
uint64_t lastfastb2 = 0;
#define longclickwaittime 350
uint64_t timerb2 = 0;
//0->b0; 1->b1; 2->b2; 3->no button; 4->long press b2, 5->loop,long press b2
uint8_t wait_for_button(bool wait = true, bool scrolling = false, bool long_click_b2 = false){
do{
if(digitalRead(b1) != bs1){
bs1 = !bs1;
delay(rebounce_delay);
if(bs1) return 0;
}
if(digitalRead(b2) != bs2){
bs2 = !bs2;
delay(rebounce_delay);
if(!long_click_b2 && bs2){
lastfastb2 = millis64();
return 1;
}
if(long_click_b2 && bs2 ) timerb2 = millis64();
else if(long_click_b2 && millis64()-timerb2 > longclickwaittime) return 4;//long click b2
else if(long_click_b2) return 1;
}
if(digitalRead(b3) != bs3){
bs3 = !bs3;
delay(rebounce_delay);
if(bs3){
lastfastb3 = millis64();
return 2;
}
}
if(bs2 && millis64() - lastfastb2 > fastwaittime && !long_click_b2){
delay(50);
return 1;
}
if(bs3 && millis64() - lastfastb3 > fastwaittime){
delay(50);
return 2;
}
if(scrolling) print_option_update();
}while(wait);
if(long_click_b2 && bs2) return 5;
return 3;
}
String digits = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912";
uint8_t pos = 0;
void mode_pi(){
pos = 0;
while(true){
u8g2.clearBuffer();
u8g2.setCursor(-pos*4+3, 7);
u8g2.print(digits);
u8g2.sendBuffer();
uint8_t b = wait_for_button(true, false);
if(b == 0) return;
if(b == 1 && pos > 0) pos--;
if(b == 2) pos++;
}
}
int8_t dir = 1; //0 up, 1 right, 2 down, 3 left
#define stepspeed 300
uint64_t timer = 0;
uint8_t tailsz = 1;
int8_t tail[32*8][2];
uint8_t apple[2];
bool lost = false;
bool finished = false;
//bool blinking = false;
uint64_t highscore = 0;
uint64_t beginning;
void mode_snake(){
beginning = millis64();
timer = millis64();
tail[0][0] = 16;
tail[0][1] = 4;
apple[0] = random(0,32);
apple[1] = random(0,8);
while(true){
if(millis64() - timer > stepspeed){
timer = millis64();
for(int8_t i = tailsz-1; i > 0; --i){
tail[i][0] = tail[i-1][0];
tail[i][1] = tail[i-1][1];
}
if(tail[0][0] == apple[0] && tail[0][1] == apple[1]){
//second part apple logic
++tailsz;
bool re = false;
do{
apple[0] = random(0,32);
apple[1] = random(0,8);
re = false;
for(uint8_t i = 0; i < tailsz; ++i) if(tail[i][0] == apple[0] && tail[i][1] == apple[1]) re = true;
}while(re);
display_nothing();
}
tail[0][-(dir%2-1)] += dir < 2 ? 1 : -1;
if(tail[0][0] < 0 || tail[0][0] > 31 || tail[0][1] < 0 || tail[0][1] > 7) lost = true;
for(int8_t i = tailsz-1; i > 0; --i){
if(tail[i][0] == tail[0][0] && tail[i][1] == tail[0][1]){
if(tailsz < 32*8) lost = true;
else{
if(!finished || (highscore > (beginning-millis64())/1000)){
finished = true;
highscore = (beginning-millis64())/1000;
print_option("You won! NEW HIGHSCORE with only " + String((beginning-millis64())/1000) + "s :) well done",false,false);
}
else{
print_option("You won in " + String((beginning-millis64())/1000) + "s!!! :) Your highscore is " + String(highscore) + "s",false,false);
}
wait_for_button(true,true);
tail[0][0] = 16;
tail[0][1] = 4;
tailsz = 1;
break;
}
}
}
if(tail[0][0] == apple[0] && tail[0][1] == apple[1]){
//first part apple logic, not getting updated next bc tailsz not increased
tail[tailsz][0] = tail[tailsz-1][0];
tail[tailsz][1] = tail[tailsz-1][1];
display_color(129,204,63,1);
}
if(lost){
if(!finished && highscore < tailsz){
highscore = tailsz;
print_option("You lost but NEW HIGHSCORE: " + String(highscore) + " :) well done",false,false);
}
else if(!finished){
print_option("You lost >:( Score: " + String(tailsz) + " Highscore: " + String(highscore),false,false);
}
else{
print_option("You lost >:( Score: " + String(tailsz),false,false);
}
display_color(204,63,63,1);
wait_for_button(true,true);
display_nothing();
tail[0][0] = 16;
tail[0][1] = 4;
tailsz = 1;
lost = false;
}
u8g2.clearBuffer();
for(uint8_t i = 0; i < tailsz; ++i){
u8g2.drawPixel(tail[i][0],tail[i][1]);
}
u8g2.drawPixel(apple[0],apple[1]);
/*if(blinking){ //wall
u8g2.drawLine(0,0,0,7);
u8g2.drawLine(0,0,31,0);
u8g2.drawLine(0,7,31,7);
u8g2.drawLine(31,0,31,7);
}*/
u8g2.sendBuffer();
}
uint8_t b = wait_for_button(false);
if(b == 0) break;
else if(b == 1){
dir++;
if(dir > 3) dir = 0;
}
else if(b == 2){
dir--;
if(dir < 0) dir = 3;
}
}
display_nothing();
}
void mode_minigame(){
counter[1] = 0;
while(true){
if(counter[1] == 0) print_option("snake", false, false);
if(counter[1] == 1) print_option("pi", false, false);
uint8_t b = wait_for_button(true, true);
if(b == 0) return;
if(b == 1 && counter[1] == 0) mode_snake();
if(b == 1 && counter[1] == 1) mode_pi();
if(b == 2){
++counter[1];
if(counter[1] > 1) counter[1] = 0;
}
}
}
void mode_edit_value(uint8_t& value, uint8_t max_, bool time = false, bool sleeptime_ = false){
uint8_t old_value = value;
while(true){
if(!time) print_option(String(value)+"/"+String(max_), false,false);
else if(!(sleeptime_ && value > max_)) print_option((value < 60?String(value)+"s":String(value-59)+"m")+"/"+String(max_-59)+"min", false,false);
else print_option("--/"+String(max_-59)+"min", false,false);
uint8_t b = wait_for_button();
if(b == 0){
value = old_value;
return;
}
else if(b == 1){
value++;
if((!sleeptime_&&value > max_)||(value > max_+1)) value = 0;
}
else if(b == 2) break;
if(time && sunset_speed == 0) sunset_speed = 1;
else if(time && sleeptime == 0) sleeptime = 1;
}
}
void mode_edit_time(uint8_t& h, uint8_t& m, bool ask = false){
uint8_t old[2] = {h,m};
uint8_t hb = 0;
while(true){
if(h < 24) print_time(h, m, hb);
else{
u8g2.clearBuffer();
u8g2.setCursor(15-8, 7);
u8g2.print("--:--");
u8g2.sendBuffer();
}
uint8_t b = wait_for_button();
if(b == 0){
h = old[0];
m = old[1];
return;
}
else if(b == 1 && hb == 0){
h++;
if(h > (ask?24:23)) h = 0;
}
else if(b == 1 && hb == 1){
m++;
if(m > 59) m = 0;
}
else if(b == 2 && hb == 0) ++hb;
else if(b == 2) return;
}
}
void mode_edit_date(uint8_t& d, uint8_t& m, uint8_t& y){
uint8_t old[3] = {d,m,y};
uint8_t edit = 0;
while(true){
u8g2.clearBuffer();
u8g2.setCursor(1,7);
u8g2.print(((d<10?"0":"")+String(d)+"/"+(m<10?"0":"")+String(m)+"/"+String(y%100)).c_str());
u8g2.drawLine(1+edit*12,7,7+edit*12,7);
u8g2.sendBuffer();
uint8_t b = wait_for_button();
if(b == 0){
d = old[0];
m = old[1];
y = old[2];
return;
}
else if(b == 1 && edit == 0){
d++;
if(d > 31) d = 1;
}
else if(b == 1 && edit == 1){
m++;
if(m > 12) m = 1;
}
else if(b == 1 && edit == 2){
y++;
if(y > old[2]+50) y = old[2];
}
else if(b == 2 && edit < 2) ++edit;
else if(b == 2 && edit == 2) return;
}
}
void mode_edit_timeoffset(){
uint64_t old = time_offset;
uint8_t edit = 0;
while(true){
u8g2.clearBuffer();
u8g2.setCursor(1,7);
u8g2.print((String(time_offset_dir?"+":"-")+(time_offset>9?"":"0")+(time_offset>99?"":"0")+(time_offset>999?"":"0")+String(time_offset)).c_str());
u8g2.drawLine(1+edit*4,7,3+edit*4,7);
u8g2.sendBuffer();
uint8_t b = wait_for_button();
if(b == 0){
time_offset = old;
return;
}
else if(b == 1){
if(edit == 0) time_offset_dir = !time_offset_dir;
else{
uint64_t magnitude = pow(10,4-edit);
if((uint64_t)(time_offset/magnitude)%10 == 9) time_offset -= magnitude*9;
else time_offset += magnitude;
}
}
else if(b == 2 && edit < 4) ++edit;
else if(b == 2 && edit >= 4) return;
}
}
#define general_options_sz 9
String general_options[general_options_sz] = {"brightness","snoozetime","sleeptime","time","date","timeoffset","save settings","load settings","test alarm"};
void mode_general_settings(){
while(true){
print_option(general_options[counter[1]],false,false);
uint8_t b = wait_for_button(true, true);
if(b == 0) return;
else if(b == 1){
if(counter[1] == 0){
mode_edit_value(display_brightness, display_brightness_max);
u8g2.setContrast(display_brightness);
}
else if(counter[1] == 1){
mode_edit_value(snooze_time, snooze_time_max, true);
}
else if(counter[1] == 2){
mode_edit_value(sleeptime, sleeptime_max, true, true);
sleepb = (sleeptime != sleeptime_max+1);
}
else if(counter[1] == 3){
mode_edit_time(h,m);
backupm = m;
backuph = h;
backupday = day;
lastrefreshed = millis64();
}
else if(counter[1] == 4){
mode_edit_date(day,mth,year);
backupm = m;
backuph = h;
backupday = day;
lastrefreshed = millis64();
}
else if(counter[1] == 5){
mode_edit_timeoffset();
}
else if(counter[1] == 8){
u8g2.clearBuffer();
u8g2.sendBuffer();
mode_alarm();
}
else if(counter[1] == 6){
prefs.begin("storage", false); //RW-mode
//pack-function uses (packed, data, bits needed). bits needed are calculated with the log2(interval-size) rounded up
packed p{0,0};
pack(p, time_offset_dir?1:0,1);//[0;1]
pack(p, time_offset,14);//[0;9999]
for(int i = 0; i < songs_sz; i++) pack(p, songs_active[i]?1:0,1);//[0;1]
pack(p, finished?1:0,1);//[0;1]
pack(p, highscore,14);//[0;10800] highscore is time in seconds. assuming that 3h is the longest one person could need.
prefs.putULong64("p1",p.data);
p = {0,0};
for(int i = 0; i < 7; i++) pack(p, alarm_time[i][0][0],5);//[0;24]
pack(p, ledcolor[0][0],8);//[0;255]
pack(p, ledcolor[0][1],8);//[0;255]
pack(p, ledcolor[0][2],8);//[0;255]
pack(p, volume,5);//[0;30]
prefs.putULong64("p2",p.data);
p = {0,0};
for(int i = 0; i < 7; i++) pack(p, alarm_time[i][0][1],5);//[0;24]
pack(p, no_alarm?1:0,1);//[0;1]
pack(p, sound_offset,7);//[0;100]
pack(p, sleepb?1:0,1);//[0;1]
pack(p, sleeptime,7);//[0;89]
pack(p, snooze_time,7);//[0;89]
pack(p, sunset_speed,7);//[0;89]
prefs.putULong64("p3",p.data);
p = {0,0};
pack(p, sunset_brightness,8);//[0;255]
pack(p, display_brightness,8);//[0;255]
pack(p, ledcolor[1][0],8);//[0;255]
pack(p, ledcolor[1][1],8);//[0;255]
pack(p, ledcolor[1][2],8);//[0;255]
pack(p, ledcolor[2][0],8);//[0;255]
pack(p, ledcolor[2][1],8);//[0;255]
pack(p, ledcolor[2][2],8);//[0;255]
prefs.putULong64("p4",p.data);
p = {0,0};
pack(p, active_sunset,3);//[0;6]
pack(p, ledcolor[3][0],8);//[0;255]
pack(p, ledcolor[3][1],8);//[0;255]
pack(p, ledcolor[3][2],8);//[0;255]
for(int i = 0; i < 7; i++) pack(p, alarm_time[i][1][0],5);//[0;24]
prefs.putULong64("p5",p.data);//62 bits -> 2 to spare
p = {0,0};
for(int i = 0; i < 7; i++) pack(p, alarm_time[i][1][1],5);//[0;24]
for(int i = 0; i < 5; i++) pack(p, alarm_time[i][2][0],5);//[0;24]
prefs.putULong64("p6",p.data);//60 bits -> 4 to spare
p = {0,0};
for(int i = 5; i < 7; i++) pack(p, alarm_time[i][2][0],5);//[0;24]
for(int i = 0; i < 7; i++) pack(p, alarm_time[i][2][1],5);//[0;24]
prefs.putULong64("p7",p.data);//45 bits -> using the 6 spare ones would give 39 bits -> no improvment
prefs.end();
print_option("done saving",false,false);
wait_for_button(true,true);
}
else if(counter[1] == 7){
bool tmp = false;
print_option("Do you really want to load the saved settings?",tmp);
while(true){
b_save = tmp?1:0;
uint8_t b_ = wait_for_button(true, true);
if(b_ == 0){tmp = false;break;}
else if(b_ == 1) tmp = !tmp;
else if(b_ == 2) break;
}
if(!tmp) continue;
prefs.begin("storage", true); //RO-mode
uint64_t p64 = prefs.getULong64("p1");
time_offset_dir = unpack(p64,1)==1;
time_offset = unpack(p64,14);
for(uint8_t i = 0; i < songs_sz; ++i) songs_active[i] = unpack(p64, 1)==1;
finished = unpack(p64,1)==1;
highscore = unpack(p64,14);
p64 = prefs.getULong64("p2");
for(uint8_t i = 0; i < 7; ++i) alarm_time[i][0][0] = unpack(p64, 5);
ledcolor[0][0] = unpack(p64,8);
ledcolor[0][1] = unpack(p64,8);
ledcolor[0][2] = unpack(p64,8);
volume = unpack(p64,5);
p64 = prefs.getULong64("p3");
for(uint8_t i = 0; i < 7; ++i) alarm_time[i][0][1] = unpack(p64, 5);
no_alarm = unpack(p64,1)==1;
sound_offset = unpack(p64,7);
sleepb = unpack(p64,1)==1;
sleeptime = unpack(p64,7);
snooze_time = unpack(p64,7);
sunset_speed = unpack(p64,7);
p64 = prefs.getULong64("p4");
sunset_brightness = unpack(p64,8);
display_brightness = unpack(p64,8);
ledcolor[1][0] = unpack(p64,8);
ledcolor[1][1] = unpack(p64,8);
ledcolor[1][2] = unpack(p64,8);
ledcolor[2][0] = unpack(p64,8);
ledcolor[2][1] = unpack(p64,8);
ledcolor[2][2] = unpack(p64,8);
p64 = prefs.getULong64("p5");
active_sunset = unpack(p64,3);
ledcolor[3][0] = unpack(p64,8);
ledcolor[3][1] = unpack(p64,8);
ledcolor[3][2] = unpack(p64,8);
for(int i = 0; i < 7; i++) alarm_time[i][1][0] = unpack(p64, 5);
p64 = prefs.getULong64("p6");
for(int i = 0; i < 7; i++) alarm_time[i][1][1] = unpack(p64, 5);
for(int i = 0; i < 5; i++) alarm_time[i][2][0] = unpack(p64, 5);
p64 = prefs.getULong64("p7");
for(int i = 5; i < 7; i++) alarm_time[i][2][0] = unpack(p64, 5);
for(int i = 0; i < 7; i++) alarm_time[i][2][1] = unpack(p64, 5);
prefs.end();
print_option("done loading",false,false);
wait_for_button(true,true);
}
}
else if(b == 2){
counter[1]++;
if(counter[1] > general_options_sz-1) counter[1] = 0;
}
}
}
void mode_music(){
while(true){
if(counter[1] > 1){
print_option(songs_name[counter[1]-2],songs_active[counter[1]-2]);
// -- PLAY SONG -------------------------------------------
#ifdef NORMAL_MODE
music_player.volume(volume);
music_player.enableLoop();
music_player.playMp3Folder(counter[1]-2+1);
#else
Serial.println("playing song: " + songs_name[counter[1]-2]);
#endif
}
else if(counter[1] == 0){
#ifdef NORMAL_MODE
music_player.stop();
#endif
print_option("volume",false,false);
}
else{
print_option("alarm offset",false,false);
}
uint8_t b = wait_for_button(true, true);
if(b == 0){
#ifdef NORMAL_MODE
music_player.stop();
#endif
return;
}
else if(b == 1 && counter[1] > 1) songs_active[counter[1]-2] = !songs_active[counter[1]-2];
else if(b == 1 && counter[1] == 0) mode_edit_value(volume, volume_max);
else if(b == 1) mode_edit_value(sound_offset, sound_offset_max);
else if(b == 2){
counter[1]++;
if(counter[1] > songs_sz-1+2) counter[1] = 0;
}
}
}
void mode_sunset(){
while(true){
if(counter[1] > 1) print_option(sunset_name[counter[1]-2], active_sunset == counter[1]-2);
else if(counter[1] == 0) print_option("speed",false,false);
else if(counter[1] == 1) print_option("brightness",false,false);
uint8_t b = wait_for_button(true, true);
if(b == 0) return;
else if(b == 1 && counter[1] > 1) active_sunset = counter[1]-2;
else if(b == 1 && counter[1] == 0) mode_edit_value(sunset_speed, sunset_speed_max, true);
else if(b == 1 && counter[1] == 1){
mode_edit_value(sunset_brightness, sunset_brightness_max);
strip.setBrightness(sunset_brightness);
}
else if(b == 2){
counter[1]++;
if(counter[1] > sunsets_sz-1+2) counter[1] = 0;
}
}
}
void mode_edit_app(){
uint32_t idx = 0;
while(true){
if(appointments.size() == 0) return;
String print_ = (appointments[idx][2]<10?"0":"")+String(appointments[idx][2])+"/"+(appointments[idx][3]<10?"0":"")+String(appointments[idx][3])+"/"+String(appointments[idx][4]%100);
if(appointments[idx][0]!=24) print_ += " "+String(appointments[idx][0])+":"+(appointments[idx][1]<10?"0":"")+String(appointments[idx][1]);
else print_ += " --:--";
print_option(print_,false,false);
uint8_t b = wait_for_button(true, true);
if(b == 0) return;
else if(b == 1){
bool del = false;
while(true){
print_option("delete",del);
uint8_t b = wait_for_button();
if(b == 0){
del = false;
break;
}
else if(b == 1) del = !del;
else if(b == 2) break;
}
if(del) appointments.erase(appointments.begin()+idx);
else if(idx > appointments.size()-1) idx = 0;
}
else if(b == 2){
++idx;
if(idx > appointments.size()-1) idx = 0;
}
}
}
void mode_alarm_app(){
uint8_t date[3] = {day,mth,year};
while(true){
if(counter[1]<1) print_option("edit",false,false);
else if(counter[1]<4) print_option(relative_times[counter[1]-1],false,false);
else print_option(((date[0]<10?"0":"")+String(date[0])+"/"+(date[1]<10?"0":"")+String(date[1])+"/"+String(date[2]%100)).c_str(),false,false);
uint8_t b = wait_for_button(true, true);
if(b == 0) return;
else if(b == 1 && counter[1] == 0) mode_edit_app();
else if(b == 1){
uint8_t t[2] = {h,m};
mode_edit_time(t[0],t[1],true);
for(uint8_t i = 0; i < appointments.size(); ++i)
if(appointments[i][2] == date[0] && appointments[i][3] == date[1] && appointments[i][4] == date[2])
appointments.erase(appointments.begin()+i);
appointments.push_back({t[0],t[1],date[0],date[1],date[2]});
return;
}
else if(b == 2){
if(counter[1]!=0) calc_next_calendar_day(date[0],date[1],date[2]);
counter[1]++;
}
}
}
void print_alarm_week(uint8_t highlight, uint8_t idx, uint8_t active){
u8g2.clearBuffer();
u8g2.setCursor(1,7);
u8g2.print(weekdays[counter[1]].c_str());
u8g2.setCursor((no_alarm||alarm_time[counter[1]][idx][0]>9?31-17:31-13),7);
u8g2.print((no_alarm||alarm_time[counter[1]][idx][0]==24?"--:--":String(alarm_time[counter[1]][idx][0])+":"+(alarm_time[counter[1]][idx][1]<10?"0":"")+String(alarm_time[counter[1]][idx][1])).c_str());
if(highlight == 0) u8g2.drawLine((alarm_time[counter[1]][idx][0]>9?31-17:31-13),7,31-11,7);
else if(highlight == 1) u8g2.drawLine(31-7,7,31-1,7);
if(highlight == 2 && active > 1){
for(uint8_t i = 0; i < active; ++i) u8g2.drawPixel((alarm_time[counter[1]][idx][0]>9?31-17+i*7:31-13+i*5),7);
u8g2.drawLine((alarm_time[counter[1]][idx][0]>9?31-17+idx*7:31-13+idx*5),7,(alarm_time[counter[1]][idx][0]>9?31-17+idx*7:31-13+idx*5)+3,7);
}
u8g2.sendBuffer();
}
uint8_t calc_active_num(uint8_t wd){
uint8_t counter = 0;
for(uint8_t i = 0; i < alarms_sz; ++i) if(alarm_time[wd][i][0] != 25) counter++;
return counter;
}
void active_plusplus(uint8_t wd, uint8_t& active){
for(uint8_t i = 0; i < alarms_sz+1; ++i){
active++;
if(active > alarms_sz-1) active = 0;
if(alarm_time[wd][active][0] != 25) break;
}
}
void clean_up_alarms_order(uint8_t idx){
for(uint8_t i = 0; i < alarms_sz;++i){
if(alarm_time[idx][i][0] == 25){
for(uint8_t a = i; a < alarms_sz;++a){
if(alarm_time[idx][a][0] != 25){
alarm_time[idx][i][0] = alarm_time[idx][a][0];
alarm_time[idx][i][1] = alarm_time[idx][a][1];
alarm_time[idx][a][0] = 25;
break;
}
}
}
}
}
void clean_up_alarms_inactive(uint8_t idx){
uint8_t real_sz = 0;
int8_t unreal_sz = 0;
for(uint8_t i = 0; i < alarms_sz;++i){
if(alarm_time[idx][i][0] < 24) ++real_sz;
else if(alarm_time[idx][i][0] == 24) ++unreal_sz;
}
for(uint8_t i = 0; i < alarms_sz;++i){
if(alarm_time[idx][i][0] == 24 && (real_sz > 0 || unreal_sz > 1)){
--unreal_sz;
alarm_time[idx][i][0] = 25;
}
}
}
void mode_alarm_week(){
uint8_t active = 0;
while(true){
if(counter[1] < 7) print_alarm_week(2,active,calc_active_num(counter[1]));
else if(counter[1] == 7) print_option("edit all",false,false);
else print_option("no alarm", no_alarm);
uint8_t b = 3;
if(counter[1] < 7 && bs2) wait_for_button(true, false, true); //wait for let go of b2
uint64_t timer = millis64();
while((b == 3 || b==5) && counter[1] < 7){
if(millis64()-timer > 1000 && b!=5){
timer = millis64();
active_plusplus(counter[1],active);
print_alarm_week(2,active,calc_active_num(counter[1]));
}
b = wait_for_button(false, false, true);
}
if(counter[1] >= 7) b = wait_for_button(true,true);
if(b == 0) break;
else if(b == 1 && counter[1] < 7){
uint8_t old[2] = {alarm_time[counter[1]][active][0],alarm_time[counter[1]][active][1]};
uint8_t hb = 0;
while(true){
print_alarm_week(hb,active,calc_active_num(counter[1]));
uint8_t b = wait_for_button();
if(b == 0){
alarm_time[counter[1]][active][0] = old[0];
alarm_time[counter[1]][active][1] = old[1];
break;
}
else if(b == 1 && hb == 0){
alarm_time[counter[1]][active][0]++;
if(alarm_time[counter[1]][active][0] > 24) alarm_time[counter[1]][active][0] = 0;
}
else if(b == 1 && hb == 1){
alarm_time[counter[1]][active][1]++;
if(alarm_time[counter[1]][active][1] > 59) alarm_time[counter[1]][active][1] = 0;
}
else if(b == 2 && hb < 1) ++hb;
else if(b == 2) break;
}
}
else if(b == 1 && counter[1] == 7){
uint8_t t[2] = {h,m};
uint8_t t_old[2] = {h,m};
mode_edit_time(t[0],t[1],true);
if(t[0] == t_old[0] && t[1] == t_old[1]) continue;
for(uint8_t i = 0; i < 5; ++i){
alarm_time[i][0][0] = t[0];
alarm_time[i][0][1] = t[1];
}
}
else if(b == 1 && counter[1] == 8) no_alarm = !no_alarm;
else if(b == 2){
counter[1]++;
if(counter[1]>8){
counter[1] = 0;
}
active = 0;
}
else if(b == 4){
uint8_t idx = 0;
while(true){
print_selection_alarm(idx);
uint8_t b_ = wait_for_button();
if(b_ == 0){
idx = 2;
break;
}
else if(b_ == 1) break;
else if(b_ == 2){
++idx;
if(idx > 2) idx = 0;
}
}
if(idx == 2) continue;
if(idx == 1 && calc_active_num(counter[1]) > 1) alarm_time[counter[1]][active][0] = 25; //inactive
else if(idx == 1) alarm_time[counter[1]][active][0] = 24; //no alarm
if(idx == 1){
clean_up_alarms_order(counter[1]);
active_plusplus(counter[1],active);
}
if(idx == 0 && calc_active_num(counter[1]) < alarms_sz){
uint8_t last = 0;
for(uint8_t i = 1; i < alarms_sz;++i) if(alarm_time[counter[1]][i][0] != 25) last = i;
alarm_time[counter[1]][last+1][0] = 0;
alarm_time[counter[1]][last+1][1] = 0;
}
}
}
for(uint8_t i = 0; i < 7;++i) clean_up_alarms_inactive(i);
for(uint8_t i = 0; i < 7;++i) clean_up_alarms_order(i);
}
void mode_led(){
while(true){
print_option(ledoptions[counter[1]], false, false);
display_nothing();
lighton = false;
uint8_t b = wait_for_button(true, true);
if(b == 0){
lighton = false;
display_nothing();
return;
}
else if(b == 2){
counter[1]++;
if(counter[1] > 6) counter[1] = 0;
}
else if(counter[1] == 0){
uint8_t i = 0;
while(true){
print_option((i==0? "r ":(i==1? "g ":"b ")) + String(tmpcolor[i]),false,false);
display_color(tmpcolor[0],tmpcolor[1],tmpcolor[2],1);
uint8_t b = wait_for_button(true, true);
if(b == 0) break;
else if(b == 1){
++tmpcolor[i];
if(tmpcolor[i] > 255) tmpcolor[i] = 0;
}
else{
++i;
if(i > 2) i = 0;
}
}
}
else if(counter[1] == 1){//{x,x,sdrt,cst,cst,cst,x}
uint8_t idx = 0;
while(true){
print_option(idx!=4?ledoptions[idx+2]:"cancel", false, false);
uint8_t b_ = wait_for_button(true,true);
if(b_ == 0){
idx = 4;
break;
}
else if(b_ == 1) break;
else if(b_ == 2){
idx++;
if(idx > 4) idx = 0;
}
}
if(idx == 4) continue;
ledcolor[idx][0] = tmpcolor[0];
ledcolor[idx][1] = tmpcolor[1];
ledcolor[idx][2] = tmpcolor[2];
}
else if(counter[1] > 1 && counter[1] < 6){
u8g2.clearBuffer();
u8g2.sendBuffer();
display_color(ledcolor[counter[1]-2][0],ledcolor[counter[1]-2][1],ledcolor[counter[1]-2][2],1);
wait_for_button();
}
else if(counter[1] == 6){
u8g2.clearBuffer();
u8g2.sendBuffer();
#define wheelspeed 3
uint64_t timer = millis64();
while(wait_for_button(false) == 3){
if(timer-millis64() > 20){
timer = millis64();
//uint32_t c = strip.gamma32(strip.ColorHSV((float)(millis64()%(uint64_t)(65536L*wheelspeed))/(wheelspeed)));
uint32_t c = strip.gamma32(strip.ColorHSV((uint64_t)(millis64()*wheelspeed)%65536L));
for(int i = 0; i < strip.numPixels(); ++i) strip.setPixelColor(i,c);
strip.show();
}
}
}
}
}
void mode_selection(){
counter[0] = 0;
while(true){
print_selection();
uint8_t b = wait_for_button();
if(b == 0) return;
else if(b == 2){
counter[0]++;
if(counter[0] > options_sz-1) counter[0] = 0;
}
else if(b == 1){
counter[1] = 0;
if(counter[0] == 0) mode_alarm_week();
else if(counter[0] == 1) mode_alarm_app();
else if(counter[0] == 2) mode_minigame();
else if(counter[0] == 3) mode_led();
else if(counter[0] == 4) mode_music();
else if(counter[0] == 5) mode_sunset();
else if(counter[0] == 6) mode_general_settings();
}
}
}
//first idx: 43, returns -1 if no easter egg
int8_t easter_egg(){
#ifdef USE_EASTEREGG
#ifdef IS_FIDO
if(day == 15 && mth == 7) return songs_sz;//bday
#else
if(day == 8 && mth == 5) return songs_sz;//bday
#endif
if(day == 24 && mth == 12) return songs_sz+1;//christmas
if(day == 1 && mth == 12) return songs_sz+2;//advent
if(day == 1 && mth == 1) return songs_sz+3;//new year
if(day == 31 && mth == 10) return songs_sz+4;//halloween
if(day == 14 && mth == 2) return songs_sz+5;//valentines day
if(day == 1 && mth == 4) return songs_sz+6;//april fools
if(day == 14 && mth == 3) return songs_sz+7;//pi day
return -1;
#endif
}
void mode_alarm(){
while(true){//snooze
// --- music -----------------------------------------------
int8_t songs_active_tmp[songs_sz] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
uint8_t idx = 0;
for(uint8_t i = 0; i < songs_sz; ++i) if(songs_active[i]){
songs_active_tmp[idx] = i;
++idx;
}
if(songs_active_tmp[0] == -1){
songs_active_tmp[idx] = 3;
++idx;
}
int8_t e_egg = easter_egg();
uint8_t play_idx = e_egg!=-1? e_egg+1 : songs_active_tmp[random(0,idx)]+1;
bool sound = false;
uint64_t alarm_start = millis64();
while(true){
float percentage = min(1.0f,((float)(millis64()-alarm_start))/((float)(sunset_speed<60?sunset_speed*1000:(sunset_speed-59)*60000)));
if(!sound && volume != 0 && percentage*100 >= sound_offset){
#ifdef NORMAL_MODE
music_player.volume(0);
music_player.enableLoop();
music_player.playMp3Folder(play_idx);
#else
Serial.println("playing: " + (e_egg==-1? songs_name[play_idx] : "mystery" + String(e_egg+1)) + "");
#endif
sound = true;
}
else if(sound){
// -- VOLUME CONTROL -------------------------------------
#ifdef NORMAL_MODE
music_player.volume((percentage-(float)sound_offset/100)*(float)volume);
#endif
}
// -- LED STRIP ------------------------------------------
uint8_t c[3] = {
sunsets[active_sunset][sunset_length-1][0],
sunsets[active_sunset][sunset_length-1][1],
sunsets[active_sunset][sunset_length-1][2]
};
if(percentage < 1.0){
float perc = (float)(sunset_length-1)*percentage-(uint8_t)((sunset_length-1)*percentage);
uint8_t idx = (uint8_t)((sunset_length-1)*percentage);
for(uint8_t i = 0; i < 3; ++i)
c[i] = (uint8_t)((1-perc) * (float)sunsets[active_sunset][idx][i] + (perc) * (float)sunsets[active_sunset][idx+1][i]);
}
display_color(c[0],c[1],c[2], percentage);
if(wait_for_button(false) != 3) break;
}
// -- STOP ALARM ---------------------------------------
display_nothing();
#ifdef NORMAL_MODE
music_player.stop();
#endif
if(snooze_time == 0) return; //no snooze
uint64_t lastsnoozetime = millis64();
while(millis64()-lastsnoozetime < (snooze_time<60?snooze_time*1000:(snooze_time-59)*60000)){
if(wait_for_button(false) != 3) return;
}
}
}
uint64_t lastprinttime = 0;
#define timeinterval 60000
void mode_time(){
while(true){//reset display and sleep
calc_time();
print_time(h,m);
lastsleep = millis64();
while(true){//inner
if(millis64() - lastprinttime > timeinterval){
calc_time();
if(day != calced_alarm_for_day) calc_next_alarm(); //calced_alarm_for_day = day;
if(day != activated_alarm_for_day && (next_alarm[0] < h || (next_alarm[0] == h && next_alarm[1] <= m))){
mode_alarm();
for(uint8_t i = 0; i < appointments.size(); ++i) if(appointments[i][4] < year || (appointments[i][4] == year && (appointments[i][3] < mth || (appointments[i][3] == mth && appointments[i][2] < day)))) appointments.erase(appointments.begin()+i);
calc_next_alarm(); //activated_alarm_for_day = ...;
sleeping = false;
break;
}
if(!sleeping) print_time(h,m);
lastprinttime = millis64() - calc_offset(millis64() - lastrefreshed)%60000;
}
uint8_t b = wait_for_button(false);
if(b != 3 && sleeping){
sleeping = false;
break;
}
if(b == 0 || ((millis64()-lastsleep > (sleeptime<60?sleeptime*1000:(sleeptime-59)*60000) && sleepb && !lighton)||(millis64()-lastsleep > 30*60*1000 && sleepb))){
//sleep
u8g2.clearBuffer();
u8g2.sendBuffer();
lighton = false;
display_nothing();
sleeping = true;
}
if(b == 1){
mode_selection();
calc_next_alarm();
break;
}
if(b == 2){
#ifdef ALARM_TEST
// -- TESTING ----------------------------------------------------------------
mode_alarm();
break;
#else
// -- real thing ------------------------------------------------------------
lighton = !lighton;
if(lighton) display_color(ledcolor[0][0],ledcolor[0][1],ledcolor[0][2],1);
else display_nothing();
break;
#endif
}
}
}
}
void setup() {
Serial.begin(115200); //other Serial to minimize confusion
pinMode(b1, INPUT);
pinMode(b2, INPUT);
pinMode(b3, INPUT);
#ifdef NORMAL_MODE
software_serial_music.begin(9600);
music_player.begin(software_serial_music);
#endif
strip.begin();
strip.setBrightness(sunset_brightness);
strip.show();
u8g2.begin();
u8g2.setContrast(10);
u8g2.setFont(u8g2_font_minuteconsole_tr);
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
print_option("Hallo :)", false, false);
wait_for_button(true,true);
//INITIALIZE DATE TIME
mode_edit_time(backuph,backupm);
lastrefreshed = millis64();
mode_edit_date(backupday,mth,year);
calc_time();
mode_time();
}
void loop(){//no need mode_time loops itself (maybe not the cleanest design idk)
}
/*
Wokwi diagram.json:
{
"version": 1,
"author": "Anonymous maker",
"editor": "wokwi",
"parts": [
{ "type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0, "attrs": {} },
{
"type": "wokwi-pushbutton",
"id": "btn1",
"top": 6.2,
"left": -124.8,
"attrs": { "color": "yellow", "xray": "1", "key": "3", "bounce": "1" }
},
{
"type": "wokwi-pushbutton",
"id": "btn2",
"top": 73.4,
"left": -124.8,
"attrs": { "color": "blue", "xray": "1" }
},
{
"type": "wokwi-pushbutton",
"id": "btn3",
"top": 140.6,
"left": -124.8,
"attrs": { "color": "green", "xray": "1" }
},
{
"type": "wokwi-resistor",
"id": "r1",
"top": 51.95,
"left": -192,
"attrs": { "value": "10000" }
},
{
"type": "wokwi-resistor",
"id": "r2",
"top": 119.15,
"left": -192,
"attrs": { "value": "10000" }
},
{
"type": "wokwi-resistor",
"id": "r3",
"top": 186.35,
"left": -192,
"attrs": { "value": "10000" }
},
{
"type": "wokwi-max7219-matrix",
"id": "matrix1",
"top": -124.2,
"left": -261.36,
"attrs": { "chain": "4", "layout": "fc16" }
},
{
"type": "wokwi-text",
"id": "text1",
"top": -9.6,
"left": -105.6,
"attrs": { "text": "Back" }
},
{ "type": "wokwi-text", "id": "text2", "top": 57.6, "left": -96, "attrs": { "text": "Ok" } },
{ "type": "wokwi-text", "id": "text3", "top": 124.8, "left": -96, "attrs": { "text": "<->" } },
{
"type": "wokwi-led-ring",
"id": "ring1",
"top": -167.29,
"left": -437.97,
"attrs": { "pixels": "12" }
}
],
"connections": [
[ "esp:TX", "$serialMonitor:RX", "", [] ],
[ "esp:RX", "$serialMonitor:TX", "", [] ],
[ "esp:GND.2", "r1:1", "black", [ "h14.44", "v-48", "h-316.8", "v76.8" ] ],
[ "r2:1", "r3:1", "black", [ "v0", "h-9.6", "v57.6" ] ],
[ "matrix1:V+", "esp:5V", "red", [ "h48", "v336", "h-144", "v-28.8" ] ],
[ "esp:GND.2", "matrix1:GND", "black", [ "v0", "h14.44", "v-163.2" ] ],
[ "matrix1:CS", "esp:2", "green", [ "h57.6", "v278.4" ] ],
[ "matrix1:CLK", "esp:4", "green", [ "h67.2", "v211.2" ] ],
[ "matrix1:DIN", "esp:15", "green", [ "h76.8", "v259.2" ] ],
[ "r1:2", "btn1:1.r", "green", [ "h94.8", "v-28.8" ] ],
[ "r1:1", "r2:1", "black", [ "v0", "h-9.6", "v57.6" ] ],
[ "r2:2", "btn2:1.r", "green", [ "h94.8", "v-38.4" ] ],
[ "r3:2", "btn3:1.r", "green", [ "h94.8", "v-38.4" ] ],
[ "esp:3V3", "btn1:2.r", "red", [ "h-14.21", "v9.6" ] ],
[ "btn1:2.r", "btn2:2.r", "red", [ "h9.8", "v67.4" ] ],
[ "btn2:2.r", "btn3:2.r", "red", [ "h9.8", "v67.4" ] ],
[ "esp:3V3", "ring1:VCC", "red", [ "h-14.21", "v-38.4", "h-336", "v19.2", "h-38.4" ] ],
[ "esp:GND.2", "ring1:GND", "black", [ "v0", "h14.44", "v-48", "h-451.2", "v38.4", "h-48" ] ],
[ "ring1:DIN", "esp:21", "green", [ "v38.4", "h38.4", "v-33.53", "h489.6", "v105.6" ] ],
[ "btn1:1.r", "esp:5", "green", [ "v0", "h38.6", "v-19.2", "h182.4", "v124.8" ] ],
[ "btn2:1.r", "esp:18", "green", [ "v0", "h38.6", "v-96", "h192", "v115.2" ] ],
[ "btn3:1.r", "esp:19", "green", [ "v0", "h48.2", "v67.2", "h192", "v-124.8" ] ]
],
"dependencies": {}
}
Wokwi libraries.txt:
# Wokwi Library List
# See https://docs.wokwi.com/guides/libraries
U8g2
Adafruit NeoPixel
*/Back
Ok
<->