1212// Version info (auto-generated)
1313#include " version_info.h"
1414
15+ // Embedded website files (auto-generated by build_website.py)
16+ #include " website/index_html.h"
17+ #include " website/messung_html.h"
18+ #include " website/justage_html.h"
19+ #include " website/ratio_html.h"
20+ #include " website/infos_html.h"
21+ #include " website/style_css.h"
22+
1523#define ADF_FREQ_MIN 2200 .0f // Min frequency for ADF4351 (2.2 GHz)
1624#define ADF_FREQ_MAX 4400 .0f // Max frequency for ADF4351 (4.4 GHz)
1725
@@ -125,20 +133,25 @@ void updateLEDs()
125133 return ;
126134 lastLEDUpdate = millis ();
127135
128- strip.setBrightness (100 ); // Set to 50% brightness
136+ // Breathing brightness: sinusoidal pulse between ~20 and 100 over ~3 seconds
137+ float breathPhase = (float )(millis () % 3000 ) / 3000 .0f ; // 0..1 over 3 s
138+ float breathVal = (sin (breathPhase * 2 .0f * PI) + 1 .0f ) / 2 .0f ; // 0..1
139+ uint8_t breathBri = (uint8_t )(20 + breathVal * 80 ); // range 20..100
129140
130141 switch (currentLEDStatus)
131142 {
132143 case LED_NO_CLIENT:
133- // White color for no client connected
144+ // White breathing for no client connected
145+ strip.setBrightness (breathBri);
134146 for (int i = 0 ; i < strip.numPixels (); i++)
135147 {
136- strip.setPixelColor (i, strip.Color (100 , 100 , 100 )); // TODO: Make it glowing?
148+ strip.setPixelColor (i, strip.Color (100 , 100 , 100 ));
137149 }
138150 break ;
139151
140152 case LED_CONNECTED:
141153 // Rainbow effect when client connected but idle
154+ strip.setBrightness (100 );
142155 for (int i = 0 ; i < strip.numPixels (); i++)
143156 {
144157 int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels ());
@@ -150,25 +163,69 @@ void updateLEDs()
150163 break ;
151164
152165 case LED_MEASURING:
153- // Red color when measuring frequency sweep
166+ // Red breathing when measuring frequency sweep
167+ strip.setBrightness (breathBri);
154168 for (int i = 0 ; i < strip.numPixels (); i++)
155169 {
156- strip.setPixelColor (i, strip.Color (100 , 0 , 0 )); // TODO: Make it glowing?
170+ strip.setPixelColor (i, strip.Color (100 , 0 , 0 ));
157171 }
158172 break ;
159173
160174 case LED_INTENSITY:
161- // Blue color when monitoring intensity for alignment
175+ // Blue breathing when monitoring intensity for alignment
176+ strip.setBrightness (breathBri);
162177 for (int i = 0 ; i < strip.numPixels (); i++)
163178 {
164- strip.setPixelColor (i, strip.Color (0 , 0 , 100 )); // TODO: Make it glowing?
179+ strip.setPixelColor (i, strip.Color (0 , 0 , 100 ));
165180 }
166181 break ;
167182 }
168183
169184 strip.show ();
170185}
171186
187+ // Utility to serve a PROGMEM string with the given content type
188+ void servePROGMEM (const char *content, const char *contentType)
189+ {
190+ server.send_P (200 , contentType, content);
191+ }
192+
193+ // Try embedded PROGMEM pages first; return true if handled.
194+ bool serveEmbeddedPage (const String &path)
195+ {
196+ if (path == " /index.html" || path == " /" )
197+ {
198+ servePROGMEM (INDEX_HTML, " text/html" );
199+ return true ;
200+ }
201+ if (path == " /messung.html" )
202+ {
203+ servePROGMEM (MESSUNG_HTML, " text/html" );
204+ return true ;
205+ }
206+ if (path == " /justage.html" )
207+ {
208+ servePROGMEM (JUSTAGE_HTML, " text/html" );
209+ return true ;
210+ }
211+ if (path == " /ratio.html" )
212+ {
213+ servePROGMEM (RATIO_HTML, " text/html" );
214+ return true ;
215+ }
216+ if (path == " /infos.html" )
217+ {
218+ servePROGMEM (INFOS_HTML, " text/html" );
219+ return true ;
220+ }
221+ if (path == " /style.css" )
222+ {
223+ servePROGMEM (STYLE_CSS, " text/css" );
224+ return true ;
225+ }
226+ return false ;
227+ }
228+
172229// Utility to serve static files from header files
173230// Determine MIME content type from file extension
174231String getContentType (const String &path)
@@ -183,7 +240,7 @@ String getContentType(const String &path)
183240 return " text/plain" ;
184241}
185242
186- // Serve files from SPIFFS, fall back to index.html for SPA navigation
243+ // Serve files: try embedded PROGMEM first, then SPIFFS fallback
187244void handleFileRequest (const String &path)
188245{
189246 String actualPath = path;
@@ -197,7 +254,11 @@ void handleFileRequest(const String &path)
197254 actualPath = " /" + actualPath;
198255 }
199256
200- // Try to serve from SPIFFS
257+ // 1) Try serving from embedded PROGMEM (fast, no flash read)
258+ if (serveEmbeddedPage (actualPath))
259+ return ;
260+
261+ // 2) Try to serve from SPIFFS (images, bootstrap, etc.)
201262 if (SPIFFS.exists (actualPath))
202263 {
203264 File file = SPIFFS.open (actualPath, " r" );
@@ -210,22 +271,13 @@ void handleFileRequest(const String &path)
210271 }
211272 }
212273
213- // File not found — for HTML navigation requests fall back to index.html
274+ // File not found — for HTML navigation requests fall back to embedded index
214275 String accept = server.header (" Accept" );
215276 if (actualPath.endsWith (" .html" ) || accept.indexOf (" text/html" ) >= 0 )
216277 {
217- Serial.print (" Unknown HTML path -> index: " );
278+ Serial.print (" Unknown HTML path -> index (PROGMEM) : " );
218279 Serial.println (actualPath);
219- if (SPIFFS.exists (" /index.html" ))
220- {
221- File idx = SPIFFS.open (" /index.html" , " r" );
222- server.streamFile (idx, " text/html" );
223- idx.close ();
224- }
225- else
226- {
227- server.send (200 , " text/html" , " <h1>ODMR Server</h1><p>SPIFFS not mounted or index.html missing.</p>" );
228- }
280+ servePROGMEM (INDEX_HTML, " text/html" );
229281 }
230282 else
231283 {
@@ -743,6 +795,11 @@ void setup()
743795 Serial.begin (115200 );
744796 delay (1500 ); // Allow time to connect
745797 Serial.println (" Booting..." );
798+ Serial.println (" =========================================" );
799+ Serial.printf (" openUC2 ODMR Server %s\n " , VERSION_STRING);
800+ Serial.printf (" Build : %s %s\n " , BUILD_DATE, BUILD_TIME);
801+ Serial.printf (" Git : %s (%s)\n " , GIT_HASH, GIT_BRANCH);
802+ Serial.println (" =========================================" );
746803
747804 disableLoopWDT (); // Deactivate Watchdog for loop
748805 // Check WiFi capabilities
@@ -890,9 +947,9 @@ void setup()
890947 // adf.updateFrequency(2.2e9); // 2.2 GHz // 1.800 GHz ─ writes R5…R0
891948
892949 // Setup routes
893- // Root handler — serve index.html from SPIFFS
950+ // Root handler — serve index.html from PROGMEM
894951 server.on (" /" , HTTP_GET, []()
895- { handleFileRequest ( " /index. html" ); });
952+ { servePROGMEM (INDEX_HTML, " text/ html" ); });
896953
897954 // Captive portal detection endpoints (P0 #1)
898955 // Returning a non-"Success" HTML page triggers the OS captive portal popup.
@@ -965,11 +1022,19 @@ void setup()
9651022 // Silently return 404 for known OS background requests (Windows Update, etc.)
9661023 // to avoid log spam from e.g. /msdownload/update/... certificate fetches
9671024 if (uri.startsWith (" /msdownload/" ) || uri.endsWith (" .cab" ) ||
968- uri.startsWith (" /GTSLT" ) || uri.startsWith (" /ocsp" ))
1025+ uri.startsWith (" /GTSLT" ) || uri.startsWith (" /ocsp" ) ||
1026+ uri.startsWith (" /en-GB/" ) || uri.startsWith (" /en-US/" ))
9691027 {
9701028 server.send (404 , " text/plain" , " " );
9711029 return ;
9721030 }
1031+ // Android / Windows append a UUID to /generate_204, e.g. /generate_204_abc123
1032+ // Match any URI starting with /generate_204 to catch these variants.
1033+ if (uri.startsWith (" /generate_204" ))
1034+ {
1035+ server.send (200 , " text/html" , PORTAL_HTML);
1036+ return ;
1037+ }
9731038 handleFileRequest (uri);
9741039 });
9751040 server.on (" /odmr_act" , HTTP_POST, handleOdmrAct);
0 commit comments