Эх сурвалжийг харах

Added captive portal redirect web server

Toby Chui 9 сар өмнө
parent
commit
7cb2000d33

+ 6 - 5
firmware/cute_useless_robot/animation.ino

@@ -2,6 +2,7 @@
 
 int frameCount = 0;
 char previousAnicode = 'a';
+const String animationFolder = "/anime/"; //Need tailing slash
 
 //delay with early breakout if current animation code changed
 void delayWithEarlyBreakout(int delayDuration) {
@@ -18,9 +19,9 @@ void delayWithEarlyBreakout(int delayDuration) {
 void handleAnimationRendering(char anicode) {
   if (!SD_exists){
     //SD card not exists. Use what is inside the buffer.
-    Serial.println("SD card not exists. Rendering internal frame buffer");
+    Serial.println("SD card not exists");
     renderFrame();
-    delay(1000);
+    delay(5000);
     return;
   }
   if (previousAnicode != anicode) {
@@ -28,7 +29,7 @@ void handleAnimationRendering(char anicode) {
     frameCount = 0;
   }
   //Check if the target animation frame is static
-  String targetFrame = "/" + String(anicode) + ".bin";
+  String targetFrame = animationFolder + String(anicode) + ".bin";
   if (SD.exists(targetFrame)) {
     //This is a static frame. Load and render it
     loadFrameAndRender(targetFrame);
@@ -38,7 +39,7 @@ void handleAnimationRendering(char anicode) {
   }
 
   //It is not a static frame. Check if x0.bin exists
-  targetFrame = "/" + String(anicode) + String(frameCount) + ".bin";
+  targetFrame = animationFolder + String(anicode) + String(frameCount) + ".bin";
   if (SD.exists(targetFrame)) {
     loadFrameAndRender(targetFrame);
     frameCount++;
@@ -48,7 +49,7 @@ void handleAnimationRendering(char anicode) {
   } else {
     //Not found.
     if (frameCount != 0) {
-      loadFrameAndRender("/" + String(anicode) + "0.bin");
+      loadFrameAndRender(animationFolder + String(anicode) + "0.bin");
       int delayDuration = getFrameDuration(anicode, 0);
       delayWithEarlyBreakout(delayDuration);
       frameCount = 1;

+ 18 - 9
firmware/cute_useless_robot/cute_useless_robot.ino

@@ -15,6 +15,9 @@
 #include <SPI.h>
 #include <SD.h>
 #include <ESP32Servo.h> //Require ESP32Servo
+#include <WiFi.h>
+#include <WebServer.h>
+#include <DNSServer.h>
 
 /* Pins Definations */
 #define STP_DATA_PIN 4    // Stepper Shift Register DS
@@ -35,6 +38,11 @@
 #define HARDWARE_TYPE MD_MAX72XX::DR1CR0RR0_HW
 #define MAX_DEVICES 8
 
+/* WiFI AP Settings */
+#define AP_SSID "(´・ω・`)"
+WebServer server(80);
+DNSServer dnsServer; 
+
 /* Calibrated offset for switch pusher servo, in degrees */
 #define SERVO_ALIGNMENT_OFFSET 1
 
@@ -63,26 +71,27 @@ void setup() {
   standbySteppers();
 
   /* Servo IO */
-  servoSwitchPusher.setPeriodHertz(50); 
-  servoCoverPusher.setPeriodHertz(50); 
+  servoSwitchPusher.setPeriodHertz(50);
+  servoCoverPusher.setPeriodHertz(50);
   servoSwitchPusher.attach(SERVO_SWITCH);
   servoCoverPusher.attach(SERVO_COVER);
   servoCoverPusher.write(0);
   servoSwitchPusher.write(0);
 
+  /* Display Module */
+  mx.begin();
+  setDisplayBrightness(0x4);
+  renderFrame();  //Render the default frame to matrix
+
   /* SD Card */
   // Initialize SD card
   if (!SD.begin(SD_CS_PIN)) {
     Serial.println("[Error] Unable to mount SD card");
+    loadSDErrorToFrameBuffer(); //Render SD ERROR to display
+    renderFrame();
     SD_exists = false;
   }
-
-  /* Display Module */
-  mx.begin();
-  setDisplayBrightness(0x4);
-  renderFrame();  //Render the default frame to matrix
-  delay(5000);
-
+  
   /* Start Dual-core processes */
   createSemaphore();
   startCoreTasks();

+ 30 - 0
firmware/cute_useless_robot/display.ino

@@ -7,6 +7,27 @@
 
 #define FRAME_BUFFER_SIZE 64  //4(32 bits) x 16 bytes
 
+//SD error frame buffer. If animation read failed, this
+//will be copied to frame buffer insteads
+const unsigned char sd_error_fb[] = {
+  0x00, 0x00, 0x00, 0x00,
+  0x77, 0x3b, 0x9c, 0x00,
+  0x44, 0xa2, 0x52, 0x00,
+  0x74, 0xbb, 0x9c, 0x00,
+  0x14, 0xa2, 0x52, 0x00,
+  0x77, 0x3a, 0x52, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00
+};
+
 //frame buffer, 32x16px
 //each LED is 1 bit
 unsigned char frame_buffer[] = {
@@ -35,6 +56,15 @@ void loadFrameAndRender(String filepath) {
   renderFrame();
 }
 
+//Load the SD read error message to framebuffer
+void loadSDErrorToFrameBuffer(){
+  //sd_error_fb and frame_buffer should be the same
+  size_t bufferSize = sizeof(sd_error_fb) / sizeof(sd_error_fb[0]);
+  for (size_t i = 0; i < bufferSize; i++) {
+    frame_buffer[i] = sd_error_fb[i];
+  }
+}
+
 //Read a binary file from SD card to current framebuffer
 int readFrameToFrameBuffer(String filepath) {
   File file = SD.open(filepath, FILE_READ);

+ 31 - 15
firmware/cute_useless_robot/tasks.ino

@@ -73,27 +73,43 @@ void startCoreTasks() {
 void PrimaryController( void * pvParameters ) {
   Serial.println("Primary logic process started on core " + String(xPortGetCoreID()));
   clearFrame();
-  int seqCounter = 0; //Modify this value to change start state of seq
+  bool switchPushed = getSwitchState();
+  if (switchPushed) {
+    /* Switch was on when device power on. Start WiFi & Web Server */
+    //Set display to AP icon
+    setAnimationCode('w');
+    //Start AP and web server
+    WiFi.softAP(AP_SSID, NULL);
+    Serial.print("Manual mode started. SSID=" + String(AP_SSID) + " listening on : ");
+    Serial.println(WiFi.softAPIP());
+    registerAPIEndpoints();
 
-  /*
+    //Setup DNS Server
+    dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
+    dnsServer.start(53, "*", WiFi.softAPIP());
+    // Start the server
+    server.begin(); 
     for (;;) {
-    runDebugSequence();
-    Serial.println("Sequence ended, restart in 3 sec");
-    delay(3000);
+      dnsServer.processNextRequest();
+      server.handleClient();
     }
-  */
-
-  for (;;) {
-    bool switchPushed = getSwitchState();
-    if (switchPushed) {
-      //Switch pushed
-      executePushAnimationSequence(seqCounter);
-      seqCounter++;
-    } else {
-      delay(100);
+  } else {
+    /* Switch is off during power on. Use automatic mode */
+    int seqCounter = 0; //Modify this value to change start state of seq
+    for (;;) {
+      switchPushed = getSwitchState();
+      if (switchPushed) {
+        //Switch pushed
+        executePushAnimationSequence(seqCounter);
+        seqCounter++;
+      } else {
+        //Prevent crashing due to infinite loop
+        delay(100);
+      }
     }
   }
 
+
 }
 
 //Core 1 code, for animation rendering

+ 105 - 0
firmware/cute_useless_robot/webserv.ino

@@ -0,0 +1,105 @@
+/* Web Server */
+const String webroot = "/web/"; //Require tailing slash
+
+void sendNotFound() {
+  server.send(404, "text/plain", "404 - Not found");
+}
+
+// Function to handle file uploads
+void handleFileUpload() {
+  HTTPUpload& upload = server.upload();
+  static File uploadFile;
+
+  if (upload.status == UPLOAD_FILE_START) {
+    String path = webroot + "uploads/" + upload.filename;
+    Serial.printf("UploadStart: %s\n", path.c_str());
+    uploadFile = SD.open(path, FILE_WRITE);
+    if (!uploadFile) {
+      Serial.println("Failed to open file for writing");
+      return;
+    }
+  } else if (upload.status == UPLOAD_FILE_WRITE) {
+    // Write the received data to the file
+    if (uploadFile) {
+      uploadFile.write(upload.buf, upload.currentSize);
+    }
+  } else if (upload.status == UPLOAD_FILE_END) {
+    // Close the file at the final chunk
+    if (uploadFile) {
+      uploadFile.close();
+      Serial.printf("UploadEnd: %s (%u)\n", upload.filename.c_str(), upload.totalSize);
+      server.send(200, "text/plain", "File Uploaded");
+    } else {
+      server.send(500, "text/plain", "File Upload Failed");
+    }
+  } else {
+    sendNotFound();
+  }
+}
+
+// Function to serve files
+void handleFileServe() {
+  String path = server.uri();
+  String filepath = path;
+  filepath.remove(0, 1); //Trim the prefix slash in uri
+  filepath = webroot + filepath;
+  Serial.println(filepath);
+  if (SD.exists(filepath)) {
+    File file = SD.open(filepath);
+    if (file) {
+      
+      server.streamFile(file, getMime(path));
+      file.close();
+    } else {
+      server.send(500, "text/plain", "500 - Internal Server Error");
+    }
+  } else {
+    handleRootRedirect();
+  }
+}
+
+//Redirect request to index.html
+void handleRootRedirect() {
+  server.sendHeader("Location", "/index.html", true);
+  server.send(302, "text/plain", "Redirecting to index.html");
+}
+
+void registerAPIEndpoints() {
+  server.on("/", HTTP_GET, handleRootRedirect);
+  server.onNotFound(handleFileServe);
+  server.on("/upload", HTTP_POST, []() {
+    server.send(200, "text/plain", "");  // Send empty response for the upload URL
+  }, handleFileUpload);
+}
+
+/* Utilities */
+String getMime(const String& path) {
+  String _contentType = "text/plain";
+  if (path.endsWith(".html")) _contentType = "text/html";
+  else if (path.endsWith(".htm")) _contentType = "text/html";
+  else if (path.endsWith(".css")) _contentType = "text/css";
+  else if (path.endsWith(".json")) _contentType = "text/json";
+  else if (path.endsWith(".js")) _contentType = "application/javascript";
+  else if (path.endsWith(".png")) _contentType = "image/png";
+  else if (path.endsWith(".gif")) _contentType = "image/gif";
+  else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
+  else if (path.endsWith(".ico")) _contentType = "image/x-icon";
+  else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
+  else if (path.endsWith(".eot")) _contentType = "font/eot";
+  else if (path.endsWith(".woff")) _contentType = "font/woff";
+  else if (path.endsWith(".woff2")) _contentType = "font/woff2";
+  else if (path.endsWith(".ttf")) _contentType = "font/ttf";
+  else if (path.endsWith(".xml")) _contentType = "text/xml";
+  else if (path.endsWith(".pdf")) _contentType = "application/pdf";
+  else if (path.endsWith(".zip")) _contentType = "application/zip";
+  else if (path.endsWith(".gz")) _contentType = "application/x-gzip";
+  else if (path.endsWith(".mp3")) _contentType = "audio/mpeg";
+  else if (path.endsWith(".mp4")) _contentType = "video/mp4";
+  else if (path.endsWith(".aac")) _contentType = "audio/aac";
+  else if (path.endsWith(".ogg")) _contentType = "audio/ogg";
+  else if (path.endsWith(".wav")) _contentType = "audio/wav";
+  else if (path.endsWith(".m4v")) _contentType = "video/x-m4v";
+  else if (path.endsWith(".webm")) _contentType = "video/webm";
+  else _contentType = "text/plain";
+  return _contentType;
+}

BIN
lasercut/OldVersions/top-1.0007.ipt


BIN
lasercut/OldVersions/top-1.0008.ipt


BIN
lasercut/all-parts_v1-1.png


BIN
lasercut/lockfile.lck


BIN
lasercut/top-1.ipt


BIN
lasercut/~all-parts_v1.dwg.13360.tmp


BIN
model/lockfile.lck


BIN
model/mockups/OldVersions/test-fit.0066.iam


BIN
model/mockups/OldVersions/test-fit.0067.iam


BIN
model/mockups/lockfile.lck


BIN
model/mockups/test-fit.iam


BIN
sd_card/anime/w.bin


+ 13 - 0
sd_card/web/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Captive Portal</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+  </head>
+  <body>
+    <h3>Captive Portal</h3>
+    <br>
+    <br>
+	<p>Hello World!</p>
+  </body>
+</html>