/*
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 77 // Antall LEDs på ringen (60 = en hel sirkel)
#define soundpin 26 // Linus sin buzzerpin
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();
void visMeny(); // Meny hvor brukeren kan aktivere alle funksjonene
// MIDDELS FUNKSJONER:
void visKlokkevisere(int time, int minutt, int sec);
//void nedtellingBar(timeinfo.tm_min, sekunderIgjen, fagFarge(gjeldendeFag), timeinfo.tm_hour, timeinfo.tm_wday);
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();
int planIndex();
// ========== 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(10);
}
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
WiFi.begin(ssid, password); // Start forbindelsen til internett
if(hentInternetTid()) {
Serial.println("✅ WiFi og tid OK!");
blinkLED(strip.Color(0, 255, 0), 2); // Grønn = success
} else {
Serial.println("❌ WiFi eller tid feilet!");
blinkLED(strip.Color(255, 0, 0), 2); // 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);
if (Serial.available()) {
String input = Serial.readStringUntil('\n'); // Leser til linjeskift
input.trim(); // Fjerner mellomrom og linjeskift
if (input == "meny") {
visMeny();
}
}
// Sjekk om det er helg
if(sjekkHelg(timeinfo.tm_wday)) {
if(!erHelg) {
erHelg = true;
Serial.println("🎉 DET ER HELG!");
}
helgAnimasjon();
delay(1000);
}
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(timeinfo.tm_min, sekunderIgjen, fagFarge(gjeldendeFag), timeinfo.tm_hour, timeinfo.tm_wday); // Vis gjenværende tid som bue
visKlokkevisere(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
}
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:
/* ERSTATTET AV NY OG KORTERE VERSJON
bool sjekkHelg(int ukedag) {
//Laget av Filip Utsola & Erik Øverland
if (ukedag == 0 || ukedag == 6){
//Serial.println("Det er helg"); // Kommentert ut av Ingve
return(true);
}
else{
//Serial.print("DEt er ikke helg"); // Kommentert ut av Ingve
return(false);
}
}
*/
bool sjekkHelg(int ukedag) {
// laget av Brage/Copilot
return (ukedag == 0 || ukedag == 6);
}
void visMeny() {
// Viser menyen
Serial.println("=== Dagens meny ===");
Serial.println("1: Menyalternativ1");
Serial.println("2: Menyalternativ2");
Serial.println("3: Menyalternativ3");
Serial.println("4: Menyalternativ4");
Serial.println("5: Menyalternativ5");
Serial.println("Velg et alternativ (1-5):");
// Venter på brukerinput
while (!Serial.available()) {
delay(200); // Kort forsinkelse for å unngå å overbelaste CPU
}
// Leser og trimmer inndata
String input = Serial.readStringUntil('\n');
input.trim(); // Fjerner mellomrom og linjeskift
// Viser mottatt input for debugging
Serial.print("Valgt alternativ: ");
Serial.println(input);
// Konverterer input til et tall for switch-case
int valg = input.toInt(); // Konverterer streng til heltall
// Håndterer menyvalg med switch-case
switch (valg) {
case 1:
Serial.println("Kjører alternativ1");
ferdigForDagenAnimasjon();
break;
case 2:
Serial.println("Kjører alternativ2");
break;
case 3:
Serial.println("Kjører blinkLED()...");
break;
case 4:
Serial.println("Viser menyen igjen...");
break;
case 5:
Serial.println("Utfører plassholderhandling...");
// Eksempel på plassholderhandling
Serial.println("Dette er en test av alternativ 5!");
break;
default:
Serial.println("Ugyldig valg! Vennligst velg et tall mellom 1 og 5.");
visMeny(); // Viser menyen igjen ved ugyldig valg
break;
}
}
uint32_t fagFarge(Fag fag) {
//Laget av Gabriela
switch (fag){
case (0): //ingenting
return(strip.Color(0,0,0));
break;
case(1): //Friminutt
return(strip.Color(250,0,250));
break;
case(2): //Elkretser
return(strip.Color(0,0,205));
break;
case(3): //ElEnergi
return(strip.Color(0,77,204));
break;
case(4): //Styresys
return(strip.Color(102,205,170));
break;
case(5):
return(strip.Color(188,143,143));
break;
case(6):
return(strip.Color(60,179,113));
break;
case(7):
return(strip.Color(100,0,0));
break;
case(8):
return(strip.Color(0,100,0));
break;
case(9):
return(strip.Color(255,165,0));
break;
default:
return(strip.Color(0,0,0));
break;
}
}
/* //Alternativ fagFarge() (Mulig den skulle hatt en default?)
uint32_t fagFarge(Fag fag) {
//Laget av Brage
switch (fag) {
case INGENTING: return strip.Color(0, 0, 0); // Svart (av)
case FRIMINUTT: return strip.Color(18, 219, 2); // Grønn
case ELKRETSER: return strip.Color(255, 195, 51); // Gul
case ELENERGI: return strip.Color(140, 237, 80); // Lys grønn
case STYRESYS: return strip.Color(138, 43, 226); // Lilla
case NORSK: return strip.Color(62, 146, 255); // lys blå
case ENGELSK: return strip.Color(0, 0, 255); // blå
case MATTE: return strip.Color(232, 0, 0); // rød
case NATURFAG: return strip.Color(216, 23, 255); // Lilla
case GYM: return strip.Color(255, 165, 0); // Oransje
}
}
*/
void blinkLED(uint32_t farge, int antallBlink) {
//Laget av Stefan
for (int i = 0; i < antallBlink; i++) {
strip.fill(farge);
strip.show();
delay(200);
strip.clear();
strip.show();
delay(200);
}
}
void buzzerBlinkLED(uint32_t farge , int antallBlink,int time_to_turn_on_ms,int time_to_turn_off_ms,int amounton,int amountoff,int hertzon,int hertzoff) {
//LAGET AV LINUS
strip.clear();
strip.show();
int availableLEDS[NUM_LEDS];
for(int i = 0; i < NUM_LEDS; i++){
availableLEDS[i] = i;
}
int x = 0;
for(int b = 0; b < antallBlink ; b++)
{
for(int i = 0; i < NUM_LEDS; i++){availableLEDS[i] = i;}
for(int i = 0; i < NUM_LEDS;i++){
x = random(0,NUM_LEDS -i);
strip.setPixelColor(availableLEDS[x],farge);
if((i+1)%amounton == 0 ){strip.show();tone(soundpin,hertzon,40);delay(time_to_turn_on_ms);}
Serial.print("Turning on led: ");
Serial.println(availableLEDS[x]);
for(int r = x; r < NUM_LEDS -i-1; r++){availableLEDS[r] = availableLEDS[r+1];}// tar den brukte LED i slutten av listen
Serial.print("last list element: ");
Serial.println(availableLEDS[NUM_LEDS-i-1]);
}
strip.show();
for(int i = 0; i < NUM_LEDS; i++){availableLEDS[i] = i;}// nullstiller listen
for(int i = 0; i < NUM_LEDS;i++){
x = random(0,NUM_LEDS-i);
strip.setPixelColor(availableLEDS[x],strip.Color(0,0,0));
if((i+1)%amountoff == 0 ){strip.show();tone(soundpin,hertzoff,20);delay(time_to_turn_off_ms);}
Serial.print("Turning off led: ");
Serial.println(availableLEDS[x]);
for(int r = x; r < NUM_LEDS -i-1 ; r++){availableLEDS[r] = availableLEDS[r+1];}
Serial.print("last list element: ");
Serial.println(availableLEDS[NUM_LEDS -i-1]);
}
strip.show();
}
}
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, int sec) {
// Laget av Robert og Linus
int t_min = time%12 * 60;
int sec_pix = ((sec*(NUM_LEDS-1))/59);
int min_pix = ((minutt*(NUM_LEDS-1))/59);
int time_pix = ((t_min + minutt) * NUM_LEDS)/720;
//strip.setPixelColor(0, strip.Color(0,255,0));
strip.setPixelColor(sec_pix, strip.Color(0 ,0 ,255));
strip.setPixelColor(min_pix, strip.Color(255, 0 ,0));
strip.setPixelColor(time_pix, strip.Color(0, 255, 0));
strip.show();
}
void nedtellingBar(int minutt, int sekunderIgjen, uint32_t fagfarge, int time, int day) {
strip.clear();
int index = planIndex(time, minutt, day);
if(index == -1){return;}
int grimsarbo = plan[index].startMinutt;
int klippoxel = plan[index].varighet;
int slutt_time = map((grimsarbo+klippoxel)*60, 0, 3600, 0, NUM_LEDS);
int start_pix = map((grimsarbo+klippoxel)*60 - sekunderIgjen, 0, 3600, 0, NUM_LEDS);
for(int inxex_pix=0 ;inxex_pix + start_pix < slutt_time && inxex_pix < 60; inxex_pix ++){
strip.setPixelColor((start_pix + inxex_pix) % NUM_LEDS, fagfarge);
}
}
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() {
// Laget av Heine
// Bruker variabelen “fade” for å få lysstrken fra 0 til 255
for (int fade = 0; fade < 256; fade += 5) {
// Brukte litt AI for å finne komboen til å lage oransje
uint32_t farge = strip.Color(fade, fade / 2, 0);
// fyller hele sirkelen med den utvalgte fargen
strip.fill(farge);
// Oppdater stripen for å vise fargen
strip.show();
// venter 50 ms får å gi det en mer “smooth” (kommer ikke på en norsk ord) overgang
delay(50);
}
// Venter ett sekund med fullt lys på
delay(1000);
// Fader lysstyrken ned fra255 til 0 gjennom å gå ned 5 per steg :)
for (int fade = 255; fade >= 0; fade -= 5) {
uint32_t farge = strip.Color(fade, fade / 2, 0);
strip.fill(farge);
strip.show();
delay(50);
}
// Slår alstå av alle LED-lysene på stripen
strip.clear();
strip.show();
}
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) {
// Laget av Robert
int index = planIndex(time, minutt, ukedag);
if(index == -1){
return INGENTING;
}
return plan[index].fag;
}
int beregnTidIgjen(int time, int minutt, int sekund, int ukedag) {
//Laget av Robert
int index = planIndex(time, minutt, ukedag);
if(index == -1){return 0;}
int time_S = time * 3600;
int minutt_S = minutt * 60;
int plan_time_s = plan[index].startTime * 3600;
int plan_minutt_s = plan[index].startMinutt * 60;
int plan_varighet_s = plan[index].varighet * 60;
int planTOT_s = plan_time_s + plan_minutt_s + plan_varighet_s;
int nåTOT_s = time_S + minutt_S + sekund;
return planTOT_s - nåTOT_s;
}
int planIndex(int time, int minutt, int ukedag){
//Laget av Robert
int minutter = time*60 + minutt;
int index = 0;
// Finner ut hvilken dag det er
while (ukedag != plan[index].dag){
if (index >= 50){
return -1;
}
index = index + 10;
}
// Sjekker om hvis det før skole start
if (minutter < plan[index].startTime*60 + plan[index].startMinutt){
return -1;
}
//Sjekker om hvis Det er etter skole tid
else if(ukedag == 3){
if(minutter > 765){return -1;}
}else if(minutter > 910){return -1;}
// Sjekker om det er innefor timens tids parameter av timen
// Vis ikke så kjekker den neste time
while(!(plan[index].startTime*60 + plan[index].startMinutt <= minutter && plan[index].startTime*60 + plan[index].startMinutt + plan[index].varighet > minutter)){
index ++;
}
// Returne Indexen til timeen vi har akkurat nå
return index;
}
bool hentInternetTid() {
//Laget av Robert
// Koble til WiFi
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(400);
attempts++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.print("Wifi status: ");
Serial.println(WiFi.status());
return false;
}
// Sett tidssone og hent tid
configTime(3600, 3600, "pool.ntp.org");
delay(2000);
time_t now;
time(&now);
return (now > 1000000); // Sjekk at vi fikk gyldig tid
}
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!
}