Browse Source

Added working controller ui

Toby Chui 9 months ago
parent
commit
7288fb430f

+ 0 - 7
firmware/cute_useless_robot/animation.ino

@@ -17,13 +17,6 @@ void delayWithEarlyBreakout(int delayDuration) {
 
 //Handle animation rendering
 void handleAnimationRendering(char anicode) {
-  if (!SD_exists){
-    //SD card not exists. Use what is inside the buffer.
-    Serial.println("SD card not exists");
-    renderFrame();
-    vTaskDelay(pdMS_TO_TICKS(5000));
-    return;
-  }
   if (previousAnicode != anicode) {
     previousAnicode = anicode;
     frameCount = 0;

+ 183 - 123
firmware/cute_useless_robot/fs.ino → firmware/cute_useless_robot/api.ino

@@ -1,123 +1,183 @@
-/* File System API */
-
-//File delete API
-void handleFileDelete(AsyncWebServerRequest *request) {
-  String path = GetPara(request, "path");
-  if (path == "") {
-    request->send(400, "text/plain", "Missing 'path' parameter");
-    return;
-  }
-  Serial.print("Requested delete path: ");
-  Serial.println(path);
-
-  if (SD.exists(path)) {
-    if (SD.remove(path)) {
-      request->send(200, "text/plain", "File removed");
-    } else {
-      request->send(500, "text/plain", "Failed to delete file");
-    }
-  } else {
-    request->send(404, "text/plain", "File not found");
-  }
-
-}
-
-//File download API
-void handleFileDownload(AsyncWebServerRequest *request) {
-  String path = GetPara(request, "path");
-  if (path == "") {
-    request->send(404, "text/plain", "'path' parameter not given");
-    return;
-  }
-  Serial.print("Requested path: ");
-  Serial.println(path);
-  if (SD.exists(path)) {
-    String contentType = getMime(path);
-    request->send(SD, path, contentType, false);
-  } else {
-    request->send(404, "text/plain", "File not found");
-  }
-}
-
-//List dir API
-void handleListDir(AsyncWebServerRequest *request) {
-  //Get the folder path to be listed
-  //As ESP8266 dont have enough memory for proper struct to json conv, we are hacking a json string out of a single for-loop
-  String jsonString = "[";
-  String folderSubPath = GetPara(request, "dir");
-  String folderPath = "/" + folderSubPath;
-  if (SD.exists(folderPath)) {
-    File root = SD.open(folderPath);
-    bool firstObject = true;
-    if (root) {
-      while (true) {
-        File entry = root.openNextFile();
-        if (!entry) {
-          // No more files
-          break;
-        } else {
-          //There are more lines. Add a , to the end of the previous json object
-          if (!firstObject) {
-            jsonString = jsonString + ",";
-          } else {
-            firstObject = false;
-          }
-        }
-
-        String isDirString = "true";
-        if (!entry.isDirectory()) {
-          isDirString = "false";
-        }
-
-        jsonString = jsonString + "{\"Filename\":\"" + entry.name() + "\",\"Filesize\":" + String(entry.size()) + ",\"IsDir\":" + isDirString + "}";
-        entry.close();
-      }
-      root.close();
-
-      jsonString += "]";
-      request->send(200, "application/json", jsonString);
-    } else {
-      request->send(500, "text/plain", "500 - Path open error");
-    }
-  } else {
-    request->send(404, "text/plain", "404 - Path not found");
-  }
-  Serial.println(folderPath);
-}
-
-// Function to handle file uploads
-void handleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
-  static File uploadFile;
-  static String uploadPath = "/";
-
-  if (request->hasParam("dir")) {
-    uploadPath = "/" + request->getParam("dir")->value() + "/";
-  }
-
-  if (index == 0) {
-    String path = uploadPath + filename;
-    Serial.printf("Upload Start: %s\n", path.c_str());
-    uploadFile = SD.open(path, FILE_WRITE);
-    if (!uploadFile) {
-      Serial.println("Failed to open file for writing");
-      return request->send(500, "text/plain", "File Upload Failed");
-    }
-  }
-
-  // Write the received data to the file
-  if (uploadFile) {
-    uploadFile.write(data, len);
-  }
-
-  if (final) {
-    // Close the file at the final chunk
-    if (uploadFile) {
-      uploadFile.close();
-      Serial.printf("Upload End: %s (%u)\n", filename.c_str(), index + len);
-      return request->send(200, "text/plain", "File Uploaded");
-    }
-    else {
-      return request->send(500, "text/plain", "File Upload Failed");
-    }
-  }
-}
+/* Control API for Robot motion system */
+//Require direction (up / down / left / right)
+//Requite state (start / stop)
+void handleMovements(AsyncWebServerRequest *request) {
+  String direction = GetPara(request, "direction");
+  String state = GetPara(request, "state");
+  
+  //Set current moving direciton
+  if (direction == "up"){
+    movingDirection = 0;
+  }else if (direction == "down"){
+    movingDirection = 1;
+  }else if (direction == "left"){
+    movingDirection = 2;
+  }else if (direction == "right"){
+    movingDirection = 3;
+  }else{
+    request->send(500, "text/plain", "500 - Invalid direction given");
+    return;
+  }
+
+  if (state == "start"){
+    //Start
+    movingActive = true;
+  }else{
+    //Stop
+    movingActive = false;
+  }
+
+  request->send(200, "text/plain", "ok");
+}
+
+//Handle request for moving the pushing arm, require angle (range 0 - 130)
+void handlePusher(AsyncWebServerRequest *request){
+  String angle = GetPara(request, "angle");
+  //Angle must be within 0 - 130 (+ offset)
+  int limitedAngle = constrain(angle.toInt(), 0, 130 + SERVO_ALIGNMENT_OFFSET);
+  servoSwitchPusher.write(limitedAngle);
+  request->send(200, "text/plain", "ok");
+}
+
+//Handle request for moving the cover, require state: {open / close}
+void handleCover(AsyncWebServerRequest *request){
+  String state = GetPara(request, "state");
+  if (state == "open"){
+    //Open
+    servoCoverPusher.write(90);
+  }else{
+    //Close
+    servoCoverPusher.write(0);
+  }
+  request->send(200, "text/plain", "ok");
+}
+
+//Render emoji to screen
+void handleRenderEmoji(AsyncWebServerRequest *request) {
+  String anicode = GetPara(request, "anicode");
+  char anicodeChar = anicode.c_str()[0];
+  setAnimationCode(anicodeChar);
+  request->send(200, "text/plain", "ok");
+}
+
+/* File System API for SD Browser*/
+
+//File delete API
+void handleFileDelete(AsyncWebServerRequest *request) {
+  String path = GetPara(request, "path");
+  if (path == "") {
+    request->send(400, "text/plain", "Missing 'path' parameter");
+    return;
+  }
+  Serial.print("Requested delete path: ");
+  Serial.println(path);
+
+  if (SD.exists(path)) {
+    if (SD.remove(path)) {
+      request->send(200, "text/plain", "File removed");
+    } else {
+      request->send(500, "text/plain", "Failed to delete file");
+    }
+  } else {
+    request->send(404, "text/plain", "File not found");
+  }
+}
+
+//File download API
+void handleFileDownload(AsyncWebServerRequest *request) {
+  String path = GetPara(request, "path");
+  if (path == "") {
+    request->send(404, "text/plain", "'path' parameter not given");
+    return;
+  }
+  Serial.print("Requested path: ");
+  Serial.println(path);
+  if (SD.exists(path)) {
+    String contentType = getMime(path);
+    request->send(SD, path, contentType, false);
+  } else {
+    request->send(404, "text/plain", "File not found");
+  }
+}
+
+//List dir API
+void handleListDir(AsyncWebServerRequest *request) {
+  //Get the folder path to be listed
+  //As ESP8266 dont have enough memory for proper struct to json conv, we are hacking a json string out of a single for-loop
+  String jsonString = "[";
+  String folderSubPath = GetPara(request, "dir");
+  String folderPath = "/" + folderSubPath;
+  if (SD.exists(folderPath)) {
+    File root = SD.open(folderPath);
+    bool firstObject = true;
+    if (root) {
+      while (true) {
+        File entry = root.openNextFile();
+        if (!entry) {
+          // No more files
+          break;
+        } else {
+          //There are more lines. Add a , to the end of the previous json object
+          if (!firstObject) {
+            jsonString = jsonString + ",";
+          } else {
+            firstObject = false;
+          }
+        }
+
+        String isDirString = "true";
+        if (!entry.isDirectory()) {
+          isDirString = "false";
+        }
+
+        jsonString = jsonString + "{\"Filename\":\"" + entry.name() + "\",\"Filesize\":" + String(entry.size()) + ",\"IsDir\":" + isDirString + "}";
+        entry.close();
+      }
+      root.close();
+
+      jsonString += "]";
+      request->send(200, "application/json", jsonString);
+    } else {
+      request->send(500, "text/plain", "500 - Path open error");
+    }
+  } else {
+    request->send(404, "text/plain", "404 - Path not found");
+  }
+  Serial.println(folderPath);
+}
+
+// Function to handle file uploads
+void handleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
+  static File uploadFile;
+  static String uploadPath = "/";
+
+  if (request->hasParam("dir")) {
+    uploadPath = "/" + request->getParam("dir")->value() + "/";
+  }
+
+  if (index == 0) {
+    String path = uploadPath + filename;
+    Serial.printf("Upload Start: %s\n", path.c_str());
+    uploadFile = SD.open(path, FILE_WRITE);
+    if (!uploadFile) {
+      Serial.println("Failed to open file for writing");
+      return request->send(500, "text/plain", "File Upload Failed");
+    }
+  }
+
+  // Write the received data to the file
+  if (uploadFile) {
+    uploadFile.write(data, len);
+  }
+
+  if (final) {
+    // Close the file at the final chunk
+    if (uploadFile) {
+      uploadFile.close();
+      Serial.printf("Upload End: %s (%u)\n", filename.c_str(), index + len);
+      return request->send(200, "text/plain", "File Uploaded");
+    } else {
+      return request->send(500, "text/plain", "File Upload Failed");
+    }
+  }
+}

+ 14 - 9
firmware/cute_useless_robot/cute_useless_robot.ino

@@ -16,7 +16,7 @@
 #include <SPI.h>
 #include <SD.h>
 #include <FS.h>
-#include <ESP32Servo.h> //Require ESP32Servo
+#include <ESP32Servo.h>  //Require ESP32Servo
 #include <WiFi.h>
 #include <ESPAsyncWebServer.h>
 #include <DNSServer.h>
@@ -42,9 +42,9 @@
 
 /* WiFI AP Settings */
 #define AP_SSID "(´・ω・`)"
-#define ENABLE_WIFI_DEBUG true //Set to true to use WiFi Client mode for remote debugging
-#define DEBUG_SSID "" //Debug SSID, usually your home WiFi SSID
-#define DEBUG_PWD "" //Debug Password, your home WiFi Password
+#define ENABLE_WIFI_DEBUG true  //Set to true to use WiFi Client mode for remote debugging
+#define DEBUG_SSID "LENS"       //Debug SSID, usually your home WiFi SSID
+#define DEBUG_PWD "1a21b998f4"  //Debug Password, your home WiFi Password
 AsyncWebServer server(80);
 DNSServer dnsServer;
 
@@ -58,9 +58,11 @@ MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DP_DATA_PIN, DP_CLK_PIN, DP_CS_PIN, MA
 
 /* Global Variables */
 char animation = 'a';  //Animation ID to render
-bool SD_exists = true; //Keep track of the animation SD card exists
+int movingDirection = 0; //Debug mode -> Moving direction
+bool movingActive = false; //Debug mode -> Is Moving direction active
 
 void setup() {
+  delay(1000);
   Serial.begin(115200);
 
   // Allow allocation of all timers
@@ -90,11 +92,14 @@ void setup() {
 
   /* SD Card */
   // Initialize SD card
-  if (!SD.begin(SD_CS_PIN,SPI,4000000U,"/sd",10U,false)) {
-    Serial.println("[Error] Unable to mount SD card");
-    loadSDErrorToFrameBuffer(); //Render SD ERROR to display
+  if (!SD.begin(SD_CS_PIN, SPI, 4000000U, "/sd", 10U, false)) {
+    Serial.println("[Error] Unable to mount SD card. Retrying in 10 seconds");
+    loadSDErrorToFrameBuffer();  //Render SD ERROR to display
     renderFrame();
-    while(1);
+
+    //Retry in 10 seconds
+    delay(5000);
+    ESP.restart();
   }
 
   /* Start Dual-core processes */

+ 89 - 40
firmware/cute_useless_robot/tasks.ino

@@ -1,5 +1,6 @@
 /* Multi-core Task Handler*/
 TaskHandle_t primaryTask;
+TaskHandle_t secondaryTask;
 TaskHandle_t animationTask;
 SemaphoreHandle_t animationMutex;
 
@@ -42,45 +43,18 @@ bool getSwitchState() {
   return (switchState == 1);
 }
 
-
-/* Multi-core process definations */
-void startCoreTasks() {
-  xTaskCreatePinnedToCore(
-    AnimationController,   /* Task function. */
-    "animator",     /* name of task. */
-    8192,       /* Stack size of task */
-    NULL,        /* parameter of the task */
-    2,           /* priority of the task */
-    &animationTask,
-    0
-  );
-  
-  xTaskCreatePinnedToCore(
-    PrimaryController,   /* Task function. */
-    "primary",     /* name of task. */
-    24576,       /* Stack size of task */
-    NULL,        /* parameter of the task */
-    1,           /* priority of the task */
-    &primaryTask,      /* Task handle to keep track of created task */
-    1
-  );
-
-  vTaskStartScheduler();
-}
-
-//For movement and primary logics
-void PrimaryController( void * pvParameters ) {
-  Serial.println("Primary logic process started on core " + String(xPortGetCoreID()));
+//Set the WiFi interface mode of the ESP32 based on start flags or debug settings
+//Return true if developer mode, return false for default mode (auto mode)
+bool initPrimaryLogicNetworkMode() {
   clearFrame();
   bool switchPushed = getSwitchState();
   if (switchPushed || ENABLE_WIFI_DEBUG) {
     /* Switch was on when device power on. Start WiFi & Web Server */
-    //Set display to AP icon
-    setAnimationCode('w');
     //Start AP and web server
     if (ENABLE_WIFI_DEBUG) {
+      setAnimationCode('x');  //WiFi Error
       //Use WiFi client mode
-      WiFi.mode(WIFI_STA); //Optional
+      WiFi.mode(WIFI_STA);  //Optional
       WiFi.begin(DEBUG_SSID, DEBUG_PWD);
       Serial.println("\nConnecting");
       while (WiFi.status() != WL_CONNECTED) {
@@ -90,7 +64,10 @@ void PrimaryController( void * pvParameters ) {
       Serial.println("\nConnected to the WiFi network");
       Serial.print("Local IP: ");
       Serial.println(WiFi.localIP());
+      setAnimationCode('w');  //WiFi Connected
     } else {
+      //Set display to AP icon
+      setAnimationCode('w');
       WiFi.softAP(AP_SSID, NULL);
       Serial.print("Manual mode started. SSID=" + String(AP_SSID) + " listening on : ");
       Serial.println(WiFi.softAPIP());
@@ -102,14 +79,81 @@ void PrimaryController( void * pvParameters ) {
     // Start the web server
     registerAPIEndpoints();
     server.begin();
-    while(1) {
-      dnsServer.processNextRequest();
-      delay(1);
+    return true;
+  }
+  return false;
+}
+
+/* Multi-core process definations */
+void startCoreTasks() {
+  //Pin animation rendering routine to core 0
+  xTaskCreatePinnedToCore(
+    AnimationController, /* Task function. */
+    "animator",          /* name of task. */
+    8192,                /* Stack size of task */
+    NULL,                /* parameter of the task */
+    2,                   /* priority of the task */
+    &animationTask,
+    0);
+
+  //Pin the primary task routine to core 1
+  xTaskCreatePinnedToCore(
+    PrimaryController, /* Task function. */
+    "primary",         /* name of task. */
+    24576,             /* Stack size of task */
+    NULL,              /* parameter of the task */
+    1,                 /* priority of the task */
+    &primaryTask,      /* Task handle to keep track of created task */
+    1);
+
+
+  xTaskCreate(
+    SecondaryController,  // Task function
+    "secondary",          // Task name
+    2048,                 // Stack size
+    NULL,                 // Task parameters
+    3,                    // Task priority
+    &secondaryTask        // Task handle
+  );
+
+
+
+  vTaskStartScheduler();
+}
+
+//For movement in development mode
+void SecondaryController(void* pvParameters) {
+  Serial.println("Secondary logic process started on core " + String(xPortGetCoreID()));
+  while (1) {
+    if (movingActive) {
+      if (movingDirection == 0) {
+        forward(1);
+      } else if (movingDirection == 1) {
+        backward(1);
+      } else if (movingDirection == 2) {
+        rotateAntiClockwise(1);
+      } else if (movingDirection == 3) {
+        rotateClockwise(1);
+      } else {
+        // Unknown direction
+        delay(100);
+        return;
+      }
+    } else {
+      // Delay for a while before checking again
+      delay(100);
     }
-  } else {
+  }
+}
+
+//For movement and primary logics
+void PrimaryController(void* pvParameters) {
+  Serial.println("Primary logic process started on core " + String(xPortGetCoreID()));
+  if (!initPrimaryLogicNetworkMode()) {
     /* Switch is off during power on. Use automatic mode */
-    int seqCounter = 0; //Modify this value to change start state of seq
-    while(1) {
+    int seqCounter = 0;  //Modify this value to change start state of seq
+    bool switchPushed = false;
+    while (1) {
       switchPushed = getSwitchState();
       if (switchPushed) {
         //Switch pushed
@@ -118,13 +162,18 @@ void PrimaryController( void * pvParameters ) {
       }
       delay(1);
     }
+  } else {
+    while (1) {
+      dnsServer.processNextRequest();
+      delay(1);
+    }
   }
 }
 
 //For animation rendering
-void AnimationController( void * pvParameters ) {
+void AnimationController(void* pvParameters) {
   Serial.println("Animation render started on core " + String(xPortGetCoreID()));
-  while(1) {
+  while (1) {
     char anicode = getAnimationCode();
     handleAnimationRendering(anicode);
     delay(1);

+ 4 - 5
firmware/cute_useless_robot/webserv.ino

@@ -8,11 +8,6 @@ void handleFileServe(AsyncWebServerRequest *request) {
   filepath.remove(0, 1); // Trim the prefix slash in uri
   filepath = webroot + filepath;
   Serial.println(filepath);
-  if (!SD_exists) {
-    // SD card not inserted
-    request->send(500, "text/plain", "500 - SD Error");
-    return;
-  }
   if (SD.exists(filepath)) {
     request->send(SD, filepath, getMime(filepath));
   } else {
@@ -43,6 +38,10 @@ void registerAPIEndpoints() {
   }, handleFileUpload);
 
   /* Application APIs */
+  server.on("/api/ctr/emoji", HTTP_GET, handleRenderEmoji);
+  server.on("/api/ctr/move", HTTP_GET, handleMovements);
+  server.on("/api/ctr/cover", HTTP_GET, handleCover);
+  server.on("/api/ctr/pusher", HTTP_GET, handlePusher);
   server.on("/api/fs/listDir", HTTP_GET, handleListDir);
   server.on("/api/fs/download", HTTP_GET, handleFileDownload);
   server.on("/api/fs/delete", HTTP_GET, handleFileDelete);

+ 5 - 1
sd_card/anime/index.txt

@@ -7,4 +7,8 @@ f => >A<
 g => OAO
 h => sad (eyes close)
 i => pre-cry
-j => cry animation
+j => cry animation
+
+w => WiFi Connected 
+x => WiFi Error
+z => Clear screen

BIN
sd_card/anime/x.bin


+ 131 - 6
sd_card/web/index.html

@@ -35,13 +35,13 @@
       
       <!-- Other Actions -->
       <div class="action-controls">
-        <button class="button">
+        <button id="coverbtn" class="button">
           <div class="header">
             <img src="img/open.svg" >
             <span>Open Cover<br>
           </div>
         </button>
-        <button class="button">
+        <button id="pusherbtn" class="button">
           <div class="header">
             <img src="img/push.svg" >
             <span>Push Switch<br>
@@ -51,16 +51,16 @@
 
       <!-- Direction Controls -->
       <div class="direction-controls">
-        <button class="button">
+        <button id="upbtn" class="button">
           <img src="img/up.svg">
         </button>
-        <button class="button">
+        <button id="downbtn" class="button">
           <img src="img/down.svg">
         </button>
-        <button class="button">
+        <button id="leftbtn" class="button">
           <img src="img/left.svg">
         </button>
-        <button class="button">
+        <button id="rightbtn" class="button">
           <img src="img/right.svg">
         </button>
       </div>
@@ -96,6 +96,131 @@
       </div>
     </div>
   <script>
+    let coverOpen = false;
+    /* Action APIS */
+    function movePusher(angle=0){
+      $.get(`/api/ctr/pusher?angle=${angle}`, function(data){
+
+      });
+    }
+
+    function moveCover(open=true){
+      $.get(`/api/ctr/cover?state=${open?"open":"close"}`, function(data){
+
+      });
+    }
+
+    function moveDirection(direction, on=false){
+      $.get(`/api/ctr/move?direction=${direction}&state=${on?"start":"stop"}`, function(data){
+
+      })
+    }
+
+
+    /* Handle Buttons Operations */
+    function openCoverStart(event) {
+      event.preventDefault();
+      console.log('Open Cover Start');
+      
+    }
+
+    function openCoverEnd(event) {
+      event.preventDefault();
+      console.log('Open Cover End');
+      if (!coverOpen){
+        moveCover(true);
+        coverOpen = true;
+      }else{
+        moveCover(false);
+        coverOpen = false;
+      }
+    }
+
+    function pushSwitchStart(event) {
+      event.preventDefault();
+      console.log('Push Switch Start');
+      movePusher(140);
+    }
+
+    function pushSwitchEnd(event) {
+      event.preventDefault();
+      console.log('Push Switch End');
+      // Add your code here
+      movePusher(0);
+    }
+
+    // Direction Controls
+    function upStart(event) {
+      event.preventDefault();
+      console.log('Up Start');
+      moveDirection("up", true);
+    }
+
+    function upEnd(event) {
+      event.preventDefault();
+      console.log('Up End');
+      moveDirection("up", false);
+    }
+
+    function downStart(event) {
+      event.preventDefault();
+      console.log('Down Start');
+      moveDirection("down", true);
+    }
+
+    function downEnd(event) {
+      event.preventDefault();
+      console.log('Down End');
+      moveDirection("down", false);
+    }
+
+    function leftStart(event) {
+      event.preventDefault();
+      console.log('Left Start');
+      moveDirection("left", true);
+    }
+
+    function leftEnd(event) {
+      event.preventDefault();
+      console.log('Left End');
+      moveDirection("left", false);
+    }
+
+    function rightStart(event) {
+      event.preventDefault();
+      console.log('Right Start');
+      moveDirection("right", true);
+    }
+
+    function rightEnd(event) {
+      event.preventDefault();
+      console.log('Right End');
+      moveDirection("right", false);
+    }
+
+    /* Button Press event handlers */
+    // Bind events to Action Controls
+    $('#coverbtn').on('mousedown touchstart', openCoverStart);
+    $('#coverbtn').on('mouseup touchend', openCoverEnd);
+
+    $('#pusherbtn').on('mousedown touchstart', pushSwitchStart);
+    $('#pusherbtn').on('mouseup touchend', pushSwitchEnd);
+
+    // Bind events to Direction Controls
+    $('#upbtn').on('mousedown touchstart', upStart);
+    $('#upbtn').on('mouseup touchend', upEnd);
+
+    $('#downbtn').on('mousedown touchstart', downStart);
+    $('#downbtn').on('mouseup touchend', downEnd);
+
+    $('#leftbtn').on('mousedown touchstart', leftStart);
+    $('#leftbtn').on('mouseup touchend', leftEnd);
+
+    $('#rightbtn').on('mousedown touchstart', rightStart);
+    $('#rightbtn').on('mouseup touchend', rightEnd);
+
+
+    /* Side menu */
     function toggleSideMenu(){
       var left = $('#sidebar').offset().left;  // Get the calculated left position
       if (left >= window.innerWidth || left == 0){

BIN
sd_card/web/src/tiny5.ttf