/* LumaDesk v1.0 — Guided starter build for Ori Alsheh, prepared 2026-10-21. Build status: planned; no physical assembly or hardware validation is claimed. Target: Arduino Mega or Mega 2560 / ATmega2560, USB power only. Libraries: LiquidCrystal (install via Library Manager if missing), EEPROM (AVR core). See BUILD_GUIDE.html for every connection and staged bring-up checks. */ #include #include #include #include #include #include "FocusCore.h" // Use the Mega's digital pins labeled 22..27, NOT the LCD pin numbers. LiquidCrystal lcd(22,23,24,25,26,27); // RS,E,D4,D5,D6,D7 constexpr uint8_t START_PIN=2, CAL_PIN=3, RED_PIN=5, GREEN_PIN=6, BLUE_PIN=7; constexpr uint8_t BUZZER_PIN=8, LIGHT_PIN=A0; constexpr bool ENABLE_BUZZER=true; // Set false if you omit the optional passive piezo. constexpr bool COMMON_ANODE=false; // Kit tutorial uses common cathode. Verify your part. constexpr bool DEMO_MODE=false; // true: 30-second focus / 10-second break. Restore false for use. LumaDesk::Timer timer(DEMO_MODE?30000UL:1500000UL,DEMO_MODE?10000UL:300000UL); LumaDesk::Button startButton,calButton; LumaDesk::Light light; uint16_t readings[8],referenceAdc=500,filteredAdc=0; uint32_t totalAdc=0,lastSample=0,lastDisplay=0,lastLog=0,messageAt=0; uint8_t sampleIndex=0; bool calibrated=false,messageVisible=false; const char* message=""; void rgb(bool red,bool green,bool blue){ digitalWrite(RED_PIN,(red!=COMMON_ANODE)?HIGH:LOW); digitalWrite(GREEN_PIN,(green!=COMMON_ANODE)?HIGH:LOW); digitalWrite(BLUE_PIN,(blue!=COMMON_ANODE)?HIGH:LOW); } void chirp(unsigned int frequency,unsigned long duration){if(ENABLE_BUZZER)tone(BUZZER_PIN,frequency,duration);} void showMessage(const char* text,uint32_t now){message=text;messageAt=now;messageVisible=true;} void loadReference(){ const uint16_t saved=(uint16_t)EEPROM.read(2)|((uint16_t)EEPROM.read(3)<<8); calibrated=EEPROM.read(0)==0x4C && EEPROM.read(1)==0x44 && saved>=40 && saved<=980; if(calibrated)referenceAdc=saved; } void calibrate(uint32_t now){ // Reject extremes; at saturation a change in light may be invisible. if(filteredAdc<40||filteredAdc>980){showMessage("REF OUT OF RANGE",now);chirp(350,120);return;} referenceAdc=filteredAdc;calibrated=true;light.dim=false; // Invalidate marker before changing data; update() avoids rewriting unchanged bytes. EEPROM.update(0,0);EEPROM.update(2,referenceAdc&0xFF);EEPROM.update(3,referenceAdc>>8); EEPROM.update(1,0x44);EEPROM.update(0,0x4C); showMessage("REFERENCE SAVED",now);chirp(1600,80); } void display(uint32_t now){ if(messageVisible && (uint32_t)(now-messageAt)>=1800)messageVisible=false; char first[17],second[17]; const uint32_t seconds=(timer.remainingMs+999UL)/1000UL; const char* mode=timer.phase==LumaDesk::Phase::Focus?"FCS":"BRK"; const char* state=timer.running?"GO":(timer.started?"PAUSE":"READY"); snprintf(first,sizeof(first),"%s %02lu:%02lu %s",mode,(unsigned long)(seconds/60),(unsigned long)(seconds%60),state); if(messageVisible)snprintf(second,sizeof(second),"%s",message); else if(!calibrated)snprintf(second,sizeof(second),"SET LIGHT REF"); else if(light.dim)snprintf(second,sizeof(second),"LOW LIGHT A%04u",filteredAdc); else snprintf(second,sizeof(second),"A%04u REF%04u",filteredAdc,referenceAdc); // Pad the rest of each row to erase stale characters without lcd.clear() flicker. lcd.setCursor(0,0);lcd.print(first);for(uint8_t i=strlen(first);i<16;i++)lcd.print(' '); lcd.setCursor(0,1);lcd.print(second);for(uint8_t i=strlen(second);i<16;i++)lcd.print(' '); } void setup(){ pinMode(START_PIN,INPUT_PULLUP);pinMode(CAL_PIN,INPUT_PULLUP); pinMode(RED_PIN,OUTPUT);pinMode(GREEN_PIN,OUTPUT);pinMode(BLUE_PIN,OUTPUT); if(ENABLE_BUZZER)pinMode(BUZZER_PIN,OUTPUT); rgb(false,false,true);Serial.begin(115200); lcd.begin(16,2);loadReference(); for(uint8_t i=0;i<8;i++){readings[i]=analogRead(LIGHT_PIN);totalAdc+=readings[i];} filteredAdc=totalAdc/8;timer.reset(millis()); showMessage("Hello, Ori!",millis()); Serial.println(F("ms,phase,state,remaining_s,adc,reference,dim,cycles")); } void loop(){ const uint32_t now=millis(); if(timer.tick(now)){chirp(1800,300);showMessage(timer.phase==LumaDesk::Phase::Break?"FOCUS COMPLETE":"BREAK COMPLETE",now);} const auto startEvent=startButton.update(digitalRead(START_PIN)==LOW,now); if(startEvent==LumaDesk::Event::ShortPress){timer.toggle(now);chirp(1200,50);} // Holding Start does nothing; short release toggles the timer. const auto calEvent=calButton.update(digitalRead(CAL_PIN)==LOW,now); if(calEvent==LumaDesk::Event::ShortPress)calibrate(now); if(calEvent==LumaDesk::Event::LongPress){timer.reset(now);showMessage("SESSION RESET",now);chirp(600,100);} if((uint32_t)(now-lastSample)>=100){ lastSample=now;totalAdc-=readings[sampleIndex];readings[sampleIndex]=analogRead(LIGHT_PIN); totalAdc+=readings[sampleIndex];sampleIndex=(sampleIndex+1)%8;filteredAdc=totalAdc/8; light.update(filteredAdc,referenceAdc,calibrated); } if(light.dim)rgb(true,true,false); // Yellow: check desk lighting, never an automatic lamp control. else if(timer.running && timer.phase==LumaDesk::Phase::Focus)rgb(false,true,false); else if(timer.started && !timer.running)rgb(true,false,true); // Magenta: paused. else rgb(false,false,true); // Blue: ready or break. if((uint32_t)(now-lastDisplay)>=200){lastDisplay=now;display(now);} if((uint32_t)(now-lastLog)>=1000){ lastLog=now;Serial.print(now);Serial.print(','); Serial.print(timer.phase==LumaDesk::Phase::Focus?F("focus"):F("break"));Serial.print(','); Serial.print(timer.running?F("running"):(timer.started?F("paused"):F("ready")));Serial.print(','); Serial.print((timer.remainingMs+999UL)/1000UL);Serial.print(',');Serial.print(filteredAdc);Serial.print(','); Serial.print(calibrated?referenceAdc:0);Serial.print(',');Serial.print(light.dim?1:0);Serial.print(',');Serial.println(timer.cycles); } }