Connecting wired and wireless communication

In thess week, our task was to setup wireless communication between circuit and internet page. I have decided to work on my project and setup communication between the esp32 and webpage, to be able to read running frequency from the microprocessor and display it on the webpage. To add something extra, I have also added option to change goal frequency, user wants to get close to. This way, the actuator doesn't vibrate with increasing frequency, but when the running frequency is getting further from the goal.

I have used this tutorial, to help me with the HTTP protocol. With the help of copilot, I have adjusted the code for my needs as you can see below:

    #include "I2Cdev.h"
    #include "MPU6050.h"
    #include 
    #include 
    #include 
    
    // Display
    #define SCREEN_WIDTH 128
    #define SCREEN_HEIGHT 32
    #define OLED_RESET    -1
    #define SCREEN_ADDRESS 0x3C  // I2C address for SSD1306
    
    // Frequency detection
    #define SAMPLE_RATE 100
    #define MAX_THRESHOLD 20384
    #define MIN_THRESHOLD 12384
    #define MA_WINDOW_SIZE 10
    #define PEAK_WINDOW_SIZE 10
    #define CLEARANCE_DELAY 2000
    
    // Network
    // Replace with your network credentials
    const char* ssid = "Your wifi";
    const char* password = "Your password";
    // Set web server port number to 80
    WiFiServer server(80);
    
    unsigned long currentTime = millis();
    unsigned long previousTime = 0; 
    const long timeoutTime = 2000;
    
    
    // Variable to store the HTTP request
    String header;
    
    Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
    
    MPU6050 mpu; // MPU6050 address is 0x68
    hw_timer_t *timer = NULL;
    volatile bool flag = false;
    
    int MOTOR_PIN = D0;   // PWM pin for motor control
    int16_t ax, ay, az;
    bool blinkState;
    
    // MPU6050 offsets
    int16_t ax_offset = -3107;
    int16_t ay_offset = 1885;
    int16_t az_offset = 837;
    uint16_t currAccel;
    
    uint16_t maBuffer[MA_WINDOW_SIZE];
    unsigned int maIndex = 0;
    uint32_t maSum = 0;
    
    unsigned long peakBuffer[PEAK_WINDOW_SIZE] = {0};
    unsigned int peakIndex = 0;
    unsigned long periodSum = 0;
    unsigned long currTimeDiff = 0;
    
    unsigned long lastTime = 0;
    uint16_t prevAvg = 0;
    uint16_t currAvg = 0;
    volatile bool highPeakFlag = false;
    volatile bool computeAvg = false;
    float currentFreq = 0.0;
    float goalFreq = 0.0;
    
    void IRAM_ATTR onTimer() {
        flag = true;
    }
    
    void setup() {
        /*--Start I2C interface--*/
        #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
        Wire.begin(); 
        #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
        Fastwire::setup(400, true);
        #endif
    
        Serial.begin(115200); 
        delay(5000);
        /*Initialize device and check connection*/ 
        Serial.println("Initializing MPU...");
        mpu.initialize();
        Serial.println("Testing MPU6050 connection...");
    
        if(mpu.testConnection() ==  false){
        Serial.println("MPU6050 connection failed");
        while(true);
        }
        else{
        Serial.println("MPU6050 connection successful");
        }
        mpu.setDLPFMode(5); 
    
        Wire.begin(6, 7);  // For Seeed XIAO ESP32
    
        if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 allocation failed"));
        while (true);
        }
        display.clearDisplay();
        display.setTextSize(1);      
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(0, 0);
        display.print("Display Ready!");
        display.display();
    
        timer = timerBegin(0, 80, true);
        timerAttachInterrupt(timer, &onTimer, true);
        timerAlarmWrite(timer, 10000, true);  // 100 Hz sampling
        timerAlarmEnable(timer);
    
        /* Setting accelerometer offsets */ 
        Serial.println("Updating internal sensor offsets...\n");
        mpu.setXAccelOffset(ax_offset); 
        mpu.setYAccelOffset(ay_offset);
        mpu.setZAccelOffset(az_offset);
    
        /*Configure board LED pin for output*/ 
        pinMode(LED_BUILTIN, OUTPUT);
        pinMode(MOTOR_PIN, OUTPUT);
        
        // Fill sample buffer
        for (int i = 0; i < MA_WINDOW_SIZE; i++) {
        mpu.getAcceleration(&ax, &ay, &az);
        currAccel = sqrt(ax * ax + ay * ay + az * az);
        maBuffer[i] = currAccel;
        maSum += currAccel;
        delay(10);
        }
        prevAvg = maSum / MA_WINDOW_SIZE;
    
        // Connect to Wi-Fi network with SSID and password
        Serial.print("Connecting to ");
        Serial.println(ssid);
        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
        }
        // Print local IP address and start web server
        Serial.println("");
        Serial.println("WiFi connected.");
        Serial.println("IP address: ");
        Serial.println(WiFi.localIP());
        server.begin();
    }
    
    void loop() {
        if (flag) {
        flag = false;
        unsigned long currentMillis = millis();
        mpu.getAcceleration(&ax, &ay, &az);
        
        currAccel = sqrt(ax * ax + ay * ay + az * az);
        maSum = maSum - maBuffer[maIndex] + currAccel;
        maBuffer[maIndex] = currAccel;
        maIndex = (maIndex + 1) % MA_WINDOW_SIZE;
        currAvg = maSum / MA_WINDOW_SIZE;
        static int lastSlope = 0;
        int slope = currAvg - prevAvg;
    
        /* Detect peaks */
        if (lastSlope > 0 && slope < 0 && prevAvg > MAX_THRESHOLD && !highPeakFlag) {
            highPeakFlag = true;
            currTimeDiff = currentMillis - lastTime;
            periodSum = periodSum - peakBuffer[peakIndex] + currTimeDiff;
            peakBuffer[peakIndex] = currTimeDiff;
            if ((peakIndex + 1) % PEAK_WINDOW_SIZE == 0) {
            computeAvg = true;
            }
            peakIndex = (peakIndex + 1) % PEAK_WINDOW_SIZE;
            lastTime = currentMillis;
        } else if (lastSlope < 0 && slope > 0 && prevAvg < MIN_THRESHOLD && highPeakFlag) {
            highPeakFlag = false;
            currTimeDiff = currentMillis - lastTime;
            periodSum = periodSum - peakBuffer[peakIndex] + currTimeDiff;
            peakBuffer[peakIndex] = currTimeDiff;
            if ((peakIndex + 1) % PEAK_WINDOW_SIZE == 0) {
            computeAvg = true;
            }
            peakIndex = (peakIndex + 1) % PEAK_WINDOW_SIZE;
            lastTime = currentMillis;
        }
    
        /* Clear buffer when no peaks detected for CLEARANCE_DELAY ms */
        if (currentMillis - lastTime > CLEARANCE_DELAY) {
            computeAvg = false;
            periodSum = 0;
            memset(peakBuffer, 0, sizeof(peakBuffer));
            peakIndex = 0;
        }
    
        /* Compute frequency */
        if (computeAvg) {
            uint32_t periodAvg = (2 * periodSum) / PEAK_WINDOW_SIZE;
            currentFreq = 1000.0 / periodAvg;
            Serial.println("Period sum: " + String(periodSum) + "\t" +  "Period avg: " + String(periodAvg) + "\t" +  "Frequency: " + String(currentFreq));
        } else {
            currentFreq = 0.0;
        }
        
        lastSlope = slope;
        prevAvg = currAvg;
        float freqDiff = abs(currentFreq - goalFreq);
        int pwmVal;
        if (currentFreq == 0.0) {
            pwmVal = 0;
        } else  {
            if (freqDiff > 0.05) {
            pwmVal = (int)((freqDiff - 0.05) * (255.0-140.0) / (2.0 - 0.05) + 140.0);
            //pwmVal = (int)((currentFreq - 1.2) * (255.0-140.0) / (3.0 - 1.2) + 140.0);
            }
            else {
            pwmVal = 0;
            }
        } 
        
        if (pwmVal > 255) {
            pwmVal = 255;
        } else if (pwmVal < 140 && pwmVal > 0) {
            pwmVal = 140;
        }else if (pwmVal < 0) {
            pwmVal = 0;
        }
    
        /* Write on display */
        display.clearDisplay();
        display.setCursor(0, 0);
        display.printf("Running frequency:\n");
        display.printf("%.2f steps per second\n", currentFreq);
        display.printf("PWM %d\n", pwmVal);
        display.display();
    
        
        analogWrite(MOTOR_PIN, pwmVal); 
    
        // Web server logic
        WiFiClient client = server.available();   // Listen for incoming clients
    
        if (client) {                             // If a new client connects,
            currentTime = millis();
            previousTime = currentTime;
            Serial.println("New Client.");          // Print a message out in the serial port
            String currentLine = "";                // Make a String to hold incoming data from the client
            while (client.connected() && currentTime - previousTime <= timeoutTime) {  // Loop while the client's connected
            currentTime = millis();
            if (client.available()) {             // If there's bytes to read from the client,
                char c = client.read();             // Read a byte, then
                Serial.write(c);                    // Print it out the serial monitor
                header += c;
                if (c == '\n') {                    // If the byte is a newline character
                // If the current line is blank, you got two newline characters in a row.
                // That's the end of the client HTTP request, so send a response:
                if (currentLine.length() == 0) {
                    // Handle requests to increase or decrease goalFreq
                    if (header.indexOf("GET /increase") >= 0) {
                    goalFreq += 0.1;  // Increase goalFreq by 0.1
                    Serial.println("Increased goalFreq: " + String(goalFreq));
                    } else if (header.indexOf("GET /decrease") >= 0) {
                    goalFreq -= 0.1;  // Decrease goalFreq by 0.1
                    if (goalFreq < 0) goalFreq = 0;  // Ensure goalFreq doesn't go below 0
                    Serial.println("Decreased goalFreq: " + String(goalFreq));
                    }
        
                    // Handle AJAX request for currentFreq
                    if (header.indexOf("GET /freq") >= 0) {
                        client.println("HTTP/1.1 200 OK");
                        client.println("Content-type:text/html");
                        client.println("Connection: close");
                        client.println();
                        client.println("<!DOCTYPE html>");
                        client.println("<html>");
                        client.println("<head>");
                        client.println("  <title>ESP32 Web Server</title>");
                        client.println("  <style>");
                        client.println("    body { font-family: sans-serif; background: #f0f0f0; text-align: center; padding: 20px; }");
                        client.println("    .box { background: #fff; padding: 20px; margin: auto; width: 90%; max-width: 300px; border-radius: 8px; box-shadow: 0 0 5px rgba(0,0,0,0.2); }");
                        client.println("    button { padding: 8px 12px; margin: 5px; border: none; border-radius: 4px; background: #007BFF; color: white; cursor: pointer; }");
                        client.println("    button:hover { background: #0056b3; }");
                        client.println("    span { display: block; text-align: left; margin-left: 5%; }");
                        client.println("  </style>");
                        client.println("</head>");
                        client.println("<body>");
                        client.println("  <div class='box'>");
                        client.println("    <h2>ESP32 Web Server</h2>");
                        client.println("    <p><span>Current Frequency:</span><span id='freq'>" + String(currentFreq, 2) + " Hz</span></p>");
                        client.println("    <p><span>Goal Frequency:</span><span id='goalFreq'>" + String(goalFreq, 2) + " Hz</span></p>");
                        client.println("    <button onclick=\"changeGoalFreq('increase')\">Increase Frequency</button>");
                        client.println("    <button onclick=\"changeGoalFreq('decrease')\">Decrease Frequency</button>");
                        client.println("    <script>");
                        client.println("      setInterval(() => {");
                        client.println("        fetch('/freq')");
                        client.println("          .then(response => response.text())");
                        client.println("          .then(data => { document.getElementById('freq').innerText = data + ' Hz'; });");
                        client.println("      }, 1000);");
                        client.println("      function changeGoalFreq(action) {");
                        client.println("        fetch('/' + action)");
                        client.println("          .then(() => {");
                        client.println("            const goalFreqElem = document.getElementById('goalFreq');");
                        client.println("            const currentGoalFreq = parseFloat(goalFreqElem.innerText);");
                        client.println("            const newGoalFreq = action === 'increase' ? (currentGoalFreq + 0.1).toFixed(2) : Math.max(0, (currentGoalFreq - 0.1).toFixed(2));");
                        client.println("            goalFreqElem.innerText = newGoalFreq + ' Hz';");
                        client.println("          });");
                        client.println("      }");
                        client.println("    </script>");
                        client.println("  </div>");
                        client.println("</body>");
                        client.println("</html>");
                        client.println("
"); client.println(""); client.println(); break; } else { // If you got a newline, then clear currentLine currentLine = ""; } } else if (c != '\r') { // If you got anything else but a carriage return character, currentLine += c; // Add it to the end of the currentLine } } } // Clear the header variable header = ""; // Close the connection client.stop(); Serial.println("Client disconnected."); Serial.println(""); } } }

I tried to make the page layout simple, yet clean.

Control webpage
Webpage for communication

The result was even better than expected, the communication was fast, stable and errorless. Most importantly, it didn't affect sample rate from the accelerometer.