/*
KLOKKEPROSJEKT 2025
====================
En smart skole-timeplan klokke med NeoPixel LED-ring
Hver elev/gruppe skal implementere sin egen funksjon.
Alle funksjoner må følge spesifikasjonene nøyaktig!
*/
#include <WiFi.h>
#include <time.h>
#include <Adafruit_NeoPixel.h>
// ========== HARDWARE KONFIGURASJON ==========
#define LED_PIN 25 // Pin for NeoPixel ring
#define NUM_LEDS 60 // Antall LEDs på ringen (60 = en hel sirkel)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
// ========== WIFI KONFIGURASJON ==========
const char* ssid = "Wokwi-GUEST"; // WiFi navn
const char* password = ""; // WiFi passord
// ========== ENUMS OG DATASTRUKTURER ==========
enum Fag {
INGENTING, FRIMINUTT, ELKRETSER, ELENERGI, STYRESYS, NORSK, ENGELSK, MATTE, NATURFAG, GYM
};
enum Ukedag {
SONDAG = 0, MANDAG, TIRSDAG, ONSDAG, TORSDAG, FREDAG, LORDAG
};
struct timePlan {
Ukedag dag; // Se enum Ukedag
byte startTime; // 0-23
byte startMinutt; // 0-59
byte varighet; // Minutter
Fag fag; // Enum for fag
};
// ========== GLOBALE VARIABLER ==========
timePlan plan[50]; // Array for hele ukens timeplan
int antallTimer = 48; // HØYESTE INDEX + 1 (ikke antall elementer!)
Fag gjeldendeFag = INGENTING; // Hvilket fag vi har nå (bruker enum)
Fag forrigeFag = INGENTING; // Hvilket fag vi hadde før
int sekunderIgjen = 0; // Sekunder igjen av gjeldende aktivitet
// Status-flagg for animasjoner
bool nyTime = false;
bool nyttFriminutt = false;
bool ferdigForDagen = false;
bool erHelg = false;
// ========== FUNKSJONSERKLÆRINGER ==========
// Disse funksjonene må elevene implementere:
// ENKLE FUNKSJONER:
bool sjekkHelg(int ukedag);
uint32_t fagFarge(Fag fag);
void blinkLED(uint32_t farge, int antallBlink);
void timeStartAnimasjon();
void friminuttAnimasjon();
// MIDDELS FUNKSJONER:
void visKlokkevisere(int time, int minutt);
void nedtellingBar(int sekunderIgjen, int totalSekunder, uint32_t farge);
void spillMelodi(int melodi);
void ferdigForDagenAnimasjon();
void helgeSluttAnimasjon();
// AVANSERTE FUNKSJONER:
void fyllPlan();
int beregnTidIgjen(int time, int minutt, int sekund, int ukedag);
Fag hentGjeldendeFag(int time, int minutt, int ukedag);
bool hentInternetTid();
void helgAnimasjon();
// ========== HJELPEFUNKSJONER (ferdig implementert) ==========
void debugInfo() {
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
Serial.print("Tid: ");
Serial.printf("%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
Serial.print(" | Ukedag: ");
Serial.print(timeinfo.tm_wday);
Serial.print(" | Fag: ");
Serial.print(gjeldendeFag);
Serial.print(" | Sekunder igjen: ");
Serial.println(sekunderIgjen);
}
void startupAnimasjon() {
// Enkel oppstart-animasjon
for(int i = 0; i < NUM_LEDS; i++) {
strip.setPixelColor(i, strip.Color(0, 50, 100));
strip.show();
delay(50);
}
strip.clear();
strip.show();
}
// ========== SETUP ==========
void setup() {
Serial.begin(115200);
Serial.println("🕐 KLOKKEPROSJEKT STARTER...");
// Initialiser LED-strip
strip.begin();
strip.show();
startupAnimasjon();
// Koble til WiFi og hent tid
if(hentInternetTid()) {
Serial.println("✅ WiFi og tid OK!");
blinkLED(strip.Color(0, 255, 0), 3); // Grønn = success
} else {
Serial.println("❌ WiFi eller tid feilet!");
blinkLED(strip.Color(255, 0, 0), 5); // Rød = feil
}
// Lag timeplan
fyllPlan();
Serial.println("📅 Timeplan lastet!");
Serial.println("🚀 Klokke klar!");
}
// ========== HOVEDLOOP ==========
void loop() {
// Hent gjeldende tid
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
// Sjekk om det er helg
if(sjekkHelg(timeinfo.tm_wday)) {
if(!erHelg) {
erHelg = true;
Serial.println("🎉 DET ER HELG!");
}
helgAnimasjon();
delay(1000);
return;
} else {
erHelg = false;
}
// Beregn gjeldende fag og tid igjen
Fag nyttFag = hentGjeldendeFag(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_wday);
sekunderIgjen = beregnTidIgjen(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, timeinfo.tm_wday);
// Sjekk om vi har byttet aktivitet
if(nyttFag != forrigeFag) {
Serial.println("🔄 NY AKTIVITET!");
if(nyttFag == INGENTING) {
ferdigForDagen = true;
Serial.println("🏠 Ferdig for dagen!");
// Sjekk om det er siste time på fredag (helgesluttanimasjon)
if(timeinfo.tm_wday == FREDAG && forrigeFag != INGENTING) {
helgeSluttAnimasjon();
}
} else if(nyttFag == FRIMINUTT) {
nyttFriminutt = true;
spillMelodi(1); // Friminutt-melodi
Serial.println("☕ FRIMINUTT!");
} else {
nyTime = true;
spillMelodi(2); // Time-melodi
Serial.println("📚 NY TIME!");
}
forrigeFag = nyttFag;
}
gjeldendeFag = nyttFag;
// Vis status på LED-ring
strip.clear();
if(ferdigForDagen) {
// Vis "ferdig for dagen" animasjon
ferdigForDagenAnimasjon();
ferdigForDagen = false;
} else if(nyttFriminutt) {
// Vis friminutt-animasjon
friminuttAnimasjon();
nyttFriminutt = false;
} else if(nyTime) {
// Vis time-start animasjon
timeStartAnimasjon();
nyTime = false;
} else {
// Normal visning: nedtelling-bue + klokkevisere
nedtellingBar(sekunderIgjen, 3600, fagFarge(gjeldendeFag)); // Vis gjenværende tid som bue
visKlokkevisere(timeinfo.tm_hour, timeinfo.tm_min);
}
strip.show();
// Debug info hver 5. sekund
static unsigned long lastDebug = 0;
if(millis() - lastDebug > 5000) {
debugInfo();
lastDebug = millis();
}
delay(1000); // Oppdater hvert sekund
}
// ========== FUNKSJONSIMPLEMENTASJONER ==========
// ELEVENE MÅ IMPLEMENTERE DISSE FUNKSJONENE:
bool sjekkHelg(int ukedag) {
// TODO: Implementer denne funksjonen
// Input: ukedag (0=søndag, 1=mandag, ..., 6=lørdag)
// Output: true hvis det er helg (lørdag eller søndag)
// TIPS til implementering:
// - Bruk if-setning eller switch-case
// - Husk: søndag = 0, lørdag = 6
// - Returner true for helg, false for ukedag
// - Bare 2-3 linjer kode trengs!
return false; // Placeholder - endre denne når du implementerer!
}
uint32_t fagFarge(Fag fag) {
// TODO: Implementer denne funksjonen
// Input: fag (enum Fag: INGENTING, FRIMINUTT, ELKRETSER, etc.)
// Output: Farge som uint32_t (bruk strip.Color(r,g,b))
// TIPS til implementering:
// - Bruk switch-case setning med alle fag-enum verdiene
// - Eksempel farger: FRIMINUTT=grønn(0,255,0), NORSK=rød(255,0,0)
// - ELKRETSER=turkis(102,205,170), MATTE=mørk grønn(0,100,0)
// - ENGELSK=lys grønn(60,179,113), GYM=oransje(255,165,0)
// - NATURFAG=lys rosa(188,143,143), INGENTING=svart(0,0,0)
// - Husk default-case for ukjente fag!
return 0; // Svart (placeholder) - endre denne når du implementerer!
}
void blinkLED(uint32_t farge, int antallBlink) {
// TODO: Implementer denne funksjonen
// Input: farge og hvor mange ganger den skal blinke
// Skal blinke hele LED-ringen
// TIPS til implementering:
// 1. Bruk for-loop for antall blink
// 2. Tenn alle LEDs: strip.fill(farge)
// 3. Vis endringen: strip.show()
// 4. Vent litt: delay(200)
// 5. Slukk alle LEDs: strip.clear()
// 6. Vis endringen: strip.show()
// 7. Vent litt: delay(200)
// 8. Gjenta for antall ganger spesifisert
// Placeholder - implementer logikken over!
}
void timeStartAnimasjon() {
// TODO: Implementer denne funksjonen
// Vis en kort animasjon når en ny time starter (Maks 5 sekunder)
// TIPS til implementering:
// - Enkel og kort animasjon som signaliserer ny time
// - Bruk fagFarge(gjeldendeFag) for å vise riktig farge
// - Eksempel: Puls-effekt, spiraleffekt, eller "countdown" fra sentrum
// - Kan kombinere med spillMelodi() for ekstra effekt
// - Hold deg under 5 sekunder total tid!
// Placeholder - implementer en kul time-start animasjon!
}
void friminuttAnimasjon() {
// TODO: Implementer denne funksjonen
// Vis en kort animasjon når friminutt starter (Maks 5 sekunder)
// TIPS til implementering:
// - Glad og energisk animasjon som signaliserer pause
// - Bruk gjerne grønne farger (friminutt = grønn)
// - Eksempel: Sprettende "ball", regnbue-flash, eller tilfeldige blitz
// - Kan kombinere med spillMelodi() for ekstra effekt
// - Hold deg under 5 sekunder total tid!
// Placeholder - implementer en glad friminutt-animasjon!
}
void visKlokkevisere(int time, int minutt) {
// TODO: Implementer denne funksjonen
// Input: time (0-23) og minutt (0-59)
// Vis timeviser (grønn) og minuttviser (rød) på LED-ringen
// TIPS til implementering:
// 1. Beregn timeviser posisjon: map(time % 12 * 60 + minutt, 0, 720, 0, NUM_LEDS-1)
// 2. Beregn minuttviser posisjon: map(minutt, 0, 59, 0, NUM_LEDS-1)
// 3. Sett timeviser: strip.setPixelColor(timePixel, strip.Color(0, 255, 0))
// 4. Sett minuttviser: strip.setPixelColor(minuttPixel, strip.Color(255, 0, 0))
// 5. Hvis begge visere på samme LED, bland fargene eller velg en
// 6. Timeviser skal bevege seg gradvis, ikke hoppe hver time!
// 7. VIKTIG: Bruk map() for å tilpasse til NUM_LEDS (fungerer med 12, 24, 60 LEDs etc.)
// Placeholder - implementer logikken over!
}
void nedtellingBar(int sekunderIgjen, int totalSekunder, uint32_t farge) {
// TODO: Implementer denne funksjonen
// Input: sekunder igjen, total tid, farge
// Vis gjenværende tid som en "bue" på klokka
// TIPS til implementering:
// 1. Beregn gjeldende minutt: (totalSekunder - sekunderIgjen) / 60
// 2. Beregn slutt-minutt: totalSekunder / 60
// 3. Konverter til LED-posisjoner: map(minutt, 0, 59, 0, NUM_LEDS-1)
// 4. Tenn LEDs fra gjeldende posisjon til slutt-posisjon
// 5. Eksempel: Klokka 11:05, time til 11:30 → tenn fra LED pos for 5min til 30min
// 6. Bruk for-loop fra startPos til sluttPos
// 7. Hvis buen går over "midnatt" (siste LED → første LED), håndter dette spesielt
// 8. VIKTIG: map() gjør at dette fungerer med alle LED-størrelser (12, 24, 60 etc.)
// Placeholder - implementer den nye klokke-baserte logikken over!
}
void spillMelodi(int melodi) {
// TODO: Implementer denne funksjonen
// Input: melodi-nummer (1=friminutt, 2=time, etc.)
// Spill en melodi (kan være tom implementasjon hvis ingen buzzer)
// TIPS til implementering - ALTERNATIV 1 (med buzzer):
// - Definer BUZZER_PIN og bruk tone(pin, frekvens, varighet)
// - Friminutt: Glad melodi (høye toner)
// - Time: Nøytral tone (middels toner)
//
// TIPS til implementering - ALTERNATIV 2 (uten buzzer):
// - Bruk LED-signaler i stedet for lyd
// - Friminutt: blinkLED(strip.Color(0, 255, 0), 3) // Grønn
// - Time: blinkLED(strip.Color(255, 0, 0), 2) // Rød
// - Lag forskjellige blinkmønstre for hver melodi
// Placeholder - velg alternativ og implementer!
}
void ferdigForDagenAnimasjon() {
// TODO: Implementer denne funksjonen
// Vis en animasjon når skoledagen er ferdig (Maks 10 sekunder)
// TIPS til implementering:
// - Avslappende og rolig animasjon som signaliserer at dagen er over
// - Bruk myke farger som fade inn/ut
// - Eksempel: Solnedgang-effekt, bølgeanimasjon, eller spirende "godnattstjerner"
// - Kan starte intenst og fade til svart på slutten
// - Maksimalt 10 sekunder - bruk tiden godt!
// - Kombiner gjerne forskjellige effekter
// Placeholder - implementer en rolig "ferdig for dagen" animasjon!
}
void helgeSluttAnimasjon() {
// TODO: Implementer denne funksjonen
// Vis en spektakulær animasjon når siste time på fredag er ferdig (Maks 30 sekunder)
// TIPS til implementering:
// - Dette er den store celebrasjonen - vær kreativ og spektakulær!
// - Kombiner flere effekter: fyrverkeri, regnbuer, puls, rotasjoner
// - Bruk alle 30 sekunder for en episk opplevelse
// - Start rolig og bygg opp til klimaks
// - Eksempel: Fyrverkeri → regnbue → feiring → rolig slutt
// - Bruk math-funksjoner for smooth overganger
// - Dette er eleven sin sjanse til å virkelig skinne!
// Placeholder - implementer en episk helgesluttanimasjon!
}
void fyllPlan() {
// MERK: Det er OK å ha hull i listen! Vi bruker forskjellige indekser for hver dag.
// Mandag (0-9)
plan[0] = {MANDAG, 10, 0, 90, STYRESYS};
plan[1] = {MANDAG, 11, 30, 30, FRIMINUTT};
plan[2] = {MANDAG, 12, 0, 45, STYRESYS};
plan[3] = {MANDAG, 12, 45, 45, ENGELSK};
plan[4] = {MANDAG, 13, 30, 10, FRIMINUTT};
plan[5] = {MANDAG, 13, 40, 90, ENGELSK};
// Tirsdag (10-19)
plan[10] = {TIRSDAG, 8, 10, 90, ENGELSK};
plan[11] = {TIRSDAG, 9, 40, 20, FRIMINUTT};
plan[12] = {TIRSDAG, 10, 0, 90, MATTE};
plan[13] = {TIRSDAG, 11, 30, 30, FRIMINUTT};
plan[14] = {TIRSDAG, 12, 0, 90, NORSK};
plan[15] = {TIRSDAG, 13, 30, 10, FRIMINUTT};
plan[16] = {TIRSDAG, 13, 40, 90, STYRESYS};
// Onsdag (20-29) - MERK: Fikset feil dag fra FREDAG til ONSDAG
plan[20] = {ONSDAG, 8, 10, 90, STYRESYS};
plan[21] = {ONSDAG, 9, 40, 20, FRIMINUTT};
plan[22] = {ONSDAG, 10, 0, 90, STYRESYS};
plan[23] = {ONSDAG, 11, 30, 30, FRIMINUTT};
plan[24] = {ONSDAG, 12, 0, 45, STYRESYS};
// Torsdag (30-39)
plan[30] = {TORSDAG, 8, 10, 90, NORSK};
plan[31] = {TORSDAG, 9, 40, 20, FRIMINUTT};
plan[32] = {TORSDAG, 10, 00, 90, ELKRETSER};
plan[33] = {TORSDAG, 11, 30, 30, FRIMINUTT};
plan[34] = {TORSDAG, 12, 0, 90, GYM};
plan[35] = {TORSDAG, 13, 30, 10, FRIMINUTT};
plan[36] = {TORSDAG, 13, 40, 90, STYRESYS};
// Fredag (40-49)
plan[40] = {FREDAG, 8, 10, 90, NATURFAG};
plan[41] = {FREDAG, 9, 40, 20, FRIMINUTT};
plan[42] = {FREDAG, 10, 0, 90, MATTE};
plan[43] = {FREDAG, 11, 30, 30, FRIMINUTT};
plan[44] = {FREDAG, 12, 0, 45, MATTE};
plan[45] = {FREDAG, 12, 45, 90, STYRESYS};
plan[46] = {FREDAG, 13, 30, 10, FRIMINUTT};
plan[47] = {FREDAG, 13, 40, 90, STYRESYS};
// antallTimer er nå 48 (høyeste index + 1)
}
Fag hentGjeldendeFag(int time, int minutt, int ukedag) {
// TODO: Implementer denne funksjonen
// Input: gjeldende tid og ukedag
// Output: hvilket fag vi har nå (enum Fag)
// TIPS til implementering:
// 1. Konverter gjeldende tid til minutter: time * 60 + minutt
// 2. Søk gjennom plan[] arrayet (index 0 til 49)
// 3. For hver oppføring, sjekk om plan[i].dag == ukedag
// 4. Beregn starttid i minutter: plan[i].startTime * 60 + plan[i].startMinutt
// 5. Beregn slutttid: starttid + plan[i].varighet
// 6. Sjekk om gjeldende tid er mellom start og slutt
// 7. Hvis ja: returner plan[i].fag
// 8. Hvis ingen match: returner INGENTING
return INGENTING; // Placeholder - endre denne når du implementerer!
}
int beregnTidIgjen(int time, int minutt, int sekund, int ukedag) {
// TODO: Implementer denne funksjonen
// Input: gjeldende tid og ukedag
// Output: hvor mange sekunder som er igjen av gjeldende aktivitet
// TIPS til implementering:
// 1. Konverter gjeldende tid til sekunder: time * 3600 + minutt * 60 + sekund
// 2. Søk gjennom plan[] arrayet (index 0 til 49)
// 3. For hver oppføring, sjekk om plan[i].dag == ukedag
// 4. Beregn starttid i sekunder: plan[i].startTime * 3600 + plan[i].startMinutt * 60
// 5. Beregn slutttid: starttid + (plan[i].varighet * 60)
// 6. Sjekk om gjeldende tid er mellom start og slutt
// 7. Hvis ja: returner (slutttid - gjeldende tid)
// 8. Hvis ingen match: returner 0
return 0; // Placeholder - endre denne når du implementerer!
}
bool hentInternetTid() {
// TODO: Implementer denne funksjonen
// Skal koble til WiFi og hente tid fra NTP-server
// Output: true hvis successful, false hvis feil
// TIPS til implementering:
// 1. Koble til WiFi: WiFi.begin(ssid, password)
// 2. Vent på tilkobling med timeout (ikke evig!)
// 3. Sjekk status: WiFi.status() == WL_CONNECTED
// 4. Sett tidssone: configTime(3600, 3600, "pool.ntp.org")
// 5. Vent litt på tidssynkronisering: delay(2000)
// 6. Sjekk at vi fikk gyldig tid: time(&now); return (now > 1000000)
// 7. Håndter feil gracefully, returner false hvis noe går galt
// 8. Bruk Serial.print() for debugging
return false; // Placeholder - implementer logikken over!
}
void helgAnimasjon() {
// TODO: Implementer denne funksjonen
// Lag en kul animasjon som vises i helgene
// TIPS til implementering - Vær kreativ! Dette er din sjanse til å skinne:
// - Regnbue-effekter: Bruk forskjellige farger som roterer
// - Bouncing balls: Simuler en ball som spretter
// - Fade-effekter: Fade inn/ut med forskjellige farger
// - Roterende mønstre: Roter et mønster rundt ringen
// - Stjernehimmel: Tilfeldige LEDs som blinker som stjerner
// - Fyrverk-effekt: Eksplosjoner av farger
// - Bruk math-funksjoner som sin(), cos() for smooth animasjoner
// - Kombiner forskjellige effekter!
// - Husk: ikke bruk for lange delays (animasjonen kalles hver loop)
// Placeholder - vær kreativ og implementer din egen kule animasjon!
}