Introduction to ESP32-S3 Communication via Wi-Fi

In this lesson, we'll explore how to establish communication between a Python script and an ESP32-S3 microcontroller using Wi-Fi in Access Point mode. We'll start with simple message exchange, move on to measuring ping times, and finally control GPIO pins and the onboard RGB LED remotely.


Table of Contents

  1. Setting Up the ESP32-S3 as an Access Point
  2. Sending Messages from Python to ESP32-S3
  3. Measuring Ping Times
  4. Controlling GPIO Pins and RGB LED
  5. Conclusion

Setting Up the ESP32-S3 as an Access Point

Before we begin, ensure you have the following:

  • An ESP32-S3 development board.
  • Arduino IDE installed with ESP32 board support.
  • Python 3 installed on your computer.
  • Your computer should have Wi-Fi capability.

We'll set up the ESP32-S3 to act as a Wi-Fi Access Point (AP) so that the Python script can connect to it directly.


Sending Messages from Python to ESP32-S3

In this section, we'll send a simple message from a Python script to the ESP32-S3 and display it on the Serial Monitor.

Python Code: Sending Messages

Copy and paste the following Python code into a file named send_message.py:

import requests

# Replace with the IP address of your ESP32-S3 AP
esp32_ip = "192.168.4.1"  # Default IP for ESP32 SoftAP

def send_message(message):
    url = f"http://{esp32_ip}/send"
    payload = {'message': message}
    try:
        response = requests.post(url, data=payload, timeout=5)
        if response.status_code == 200:
            print("Message sent successfully")
        else:
            print(f"Failed to send message. Status code: {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"Error communicating with ESP32: {e}")

if __name__ == "__main__":
    # Ensure your computer is connected to the ESP32 AP before running the script
    while True:
        message = input("Enter a message to send to ESP32 (type 'exit' to quit): ")
        if message.lower() == 'exit':
            break
        send_message(message)

Description:

  • The script prompts the user to enter a message.
  • It sends the message to the ESP32-S3 over a TCP socket.
  • Ensure your computer is connected to the ESP32-S3's Wi-Fi network.

Arduino Code: Receiving Messages

Copy and paste the following Arduino code into the Arduino IDE and upload it to your ESP32-S3:

#include <WiFi.h>
#include <WebServer.h>

// Replace with your desired AP credentials
const char* ssid = "ESP32-AP";
const char* password = "12345678";  // Minimum 8 characters for WPA2

WebServer server(80);  // Create a web server on port 80

void handleRoot() {
  // Simple response to indicate the server is running
  server.send(200, "text/plain", "ESP32 Web Server is running");
}

void handleMessage() {
  if (server.hasArg("message")) {
    String message = server.arg("message");
    Serial.print("Received message: ");
    Serial.println(message);
    server.send(200, "text/plain", "Message received");
  } else {
    server.send(400, "text/plain", "Bad Request: 'message' argument missing");
  }
}

void setup() {
  Serial.begin(115200);

  // Configure ESP32 as an Access Point
  Serial.println("Setting up Access Point...");
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);

  // Define routes
  server.on("/", handleRoot);
  server.on("/send", handleMessage);

  // Start the server
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  server.handleClient();
}

Description:

  • Sets up the ESP32-S3 as a Wi-Fi Access Point.
  • Listens for incoming TCP connections on port 80.
  • Receives messages and prints them to the Serial Monitor.

Instructions:

  1. Replace ssid and password with your desired Wi-Fi credentials.
  2. Upload the code to your ESP32-S3.
  3. Open the Serial Monitor at 115200 baud rate to view incoming messages.
  4. Connect your computer to the ESP32-S3's Wi-Fi network.
  5. Run the Python script and send messages.

Measuring Ping Times

Now, we'll create a ping test to measure the communication latency between the Python script and the ESP32-S3.

Python Code: Ping Test

Copy and paste the following Python code into a file named ping_test.py:

import socket
import time
import matplotlib.pyplot as plt

# ESP32 Access Point IP and Port
ESP32_IP = '192.168.4.1'
PORT = 80
PING_COUNT = 1000

def main():
    ping_times = []

    for i in range(PING_COUNT):
        start_time = time.time()
        success = send_ping()
        end_time = time.time()

        if success:
            ping_time = (end_time - start_time) * 1000  # Convert to milliseconds
            ping_times.append(ping_time)
            print(f"Ping {i+1}: {ping_time:.2f} ms")
        else:
            print(f"Ping {i+1}: Failed")

    average_ping = sum(ping_times) / len(ping_times)
    print(f"\nAverage Ping Time: {average_ping:.2f} ms")

    # Plotting the ping times
    plt.plot(ping_times)
    plt.xlabel('Ping Number')
    plt.ylabel('Ping Time (ms)')
    plt.title('Ping Times over 1000 Samples')
    plt.show()

def send_ping():
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)  # Timeout after 1 second
        sock.connect((ESP32_IP, PORT))
        sock.sendall(b'ping\n')
        sock.close()
        return True
    except Exception as e:
        return False

if __name__ == '__main__':
    main()

Description:

  • Sends 'ping' messages to the ESP32-S3.
  • Measures the time taken for each ping.
  • Plots the ping times over 1000 samples.
  • Calculates the average ping time.

Arduino Code: Ping Response

Copy and paste the following Arduino code into the Arduino IDE and upload it to your ESP32-S3:

#include <WiFi.h>

// Wi-Fi credentials
const char* ssid = "ESP32_AP";
const char* password = "12345678";

// Create a Wi-Fi server on port 80
WiFiServer server(80);

void setup() {
  Serial.begin(115200);

  // Initialize Wi-Fi in AP mode
  WiFi.softAP(ssid, password);
  Serial.println();
  Serial.print("Access Point \"");
  Serial.print(ssid);
  Serial.println("\" started");
  Serial.print("IP address: ");
  Serial.println(WiFi.softAPIP());

  // Start the server
  server.begin();
}

void loop() {
  WiFiClient client = server.available(); // Listen for incoming clients

  if (client) {
    String currentLine = "";

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        if (c == '\n') {
          // End of message
          if (currentLine == "ping") {
            client.print("pong\n");
          }
          currentLine = "";
          break; // Exit after responding
        } else {
          currentLine += c;
        }
      }
    }

    client.stop();
  }
}

Description:

  • Responds with 'pong' when it receives a 'ping'.
  • Minimal processing to ensure accurate timing.
  • No Serial Monitor output to reduce latency.

Instructions:

  1. Ensure the Wi-Fi credentials match between the Python and Arduino code.
  2. Install the matplotlib library for Python if you haven't already (pip install matplotlib).
  3. Run the Python script to start the ping test.
  4. Wait for the test to complete and observe the plotted results.

Controlling GPIO Pins and RGB LED

Finally, we'll implement a system to control GPIO pins and the onboard RGB LED by sending commands from the Python script.

Python Code: Remote Control

Copy and paste the following Python code into a file named remote_control.py:

import socket

# ESP32 Access Point IP and Port
ESP32_IP = '192.168.4.1'
PORT = 80

def main():
    while True:
        command = input("Enter command: ").lower().strip()
        send_command(command)

def send_command(command):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((ESP32_IP, PORT))
        sock.sendall((command + '\n').encode('utf-8'))
        sock.close()
        print("Command sent!")
    except Exception as e:
        print(f"Failed to send command: {e}")

if __name__ == '__main__':
    main()

Description:

  • Sends user-entered commands to the ESP32-S3.
  • Converts commands to lowercase and strips whitespace.
  • Commands can control GPIO pins and the RGB LED.

Arduino Code: Command Handling

Copy and paste the following Arduino code into the Arduino IDE and upload it to your ESP32-S3:

#include <WiFi.h>

// Wi-Fi credentials
const char* ssid = "ESP32_AP";
const char* password = "12345678";

// Create a Wi-Fi server on port 80
WiFiServer server(80);

// Avoid controlling pins 19 and 20
const int FORBIDDEN_PINS[] = {19, 20};

// RGB LED pins
const int LED_R_PIN = 21;
const int LED_G_PIN = 22;
const int LED_B_PIN = 23;

// Current LED color
int currentR = 0, currentG = 0, currentB = 0;

void setup() {
  Serial.begin(115200);

  // Initialize Wi-Fi in AP mode
  WiFi.softAP(ssid, password);
  Serial.println();
  Serial.print("Access Point \"");
  Serial.print(ssid);
  Serial.println("\" started");
  Serial.print("IP address: ");
  Serial.println(WiFi.softAPIP());

  // Start the server
  server.begin();

  // Set up RGB LED pins
  pinMode(LED_R_PIN, OUTPUT);
  pinMode(LED_G_PIN, OUTPUT);
  pinMode(LED_B_PIN, OUTPUT);

  // Turn off the LED
  setLEDColor(0, 0, 0);
}

void loop() {
  WiFiClient client = server.available(); // Listen for incoming clients

  if (client) {
    String currentLine = "";

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        if (c == '\n') {
          // End of command
          handleCommand(currentLine);
          currentLine = "";
          break; // Exit after handling the command
        } else {
          currentLine += c;
        }
      }
    }

    client.stop();
  }
}

// Handle incoming commands
void handleCommand(String command) {
  command.trim();
  command.toLowerCase();

  // Control GPIO pins
  if (command.startsWith("turn on pin ")) {
    int pin = command.substring(11).toInt();
    controlPin(pin, HIGH);
  } else if (command.startsWith("turn off pin ")) {
    int pin = command.substring(12).toInt();
    controlPin(pin, LOW);
  } else if (command.startsWith("pin ")) {
    int spaceIndex = command.indexOf(' ', 4);
    int pin = command.substring(4, spaceIndex).toInt();
    String action = command.substring(spaceIndex + 1);

    if (action == "on") {
      controlPin(pin, HIGH);
    } else if (action == "off") {
      controlPin(pin, LOW);
    } else if (action.endsWith("%")) {
      int percent = action.substring(0, action.length() - 1).toInt();
      setPinPWM(pin, percent);
    }
  }
  // Control RGB LED
  else if (command.startsWith("led turn on")) {
    setLEDColor(255, 255, 255);
  } else if (command.startsWith("led turn off")) {
    setLEDColor(0, 0, 0);
  } else if (command.startsWith("led ")) {
    String colorValue = command.substring(4);
    if (colorValue.indexOf('-') != -1) {
      // RGB values
      int r, g, b;
      if (parseRGB(colorValue, r, g, b)) {
        setLEDColor(r, g, b);
      }
    } else {
      // Color name
      int r, g, b;
      if (getColorFromName(colorValue, r, g, b)) {
        setLEDColor(r, g, b);
      }
    }
  } else if (command.startsWith("set color ")) {
    String colorName = command.substring(10);
    int r, g, b;
    if (getColorFromName(colorName, r, g, b)) {
      setLEDColor(r, g, b);
    }
  } else if (command.startsWith("lower brightness by ")) {
    int percent = command.substring(19).toInt();
    lowerBrightness(percent);
  } else {
    Serial.println("Unknown command: " + command);
  }
}

// Control GPIO pins
void controlPin(int pin, int state) {
  if (isForbiddenPin(pin)) {
    Serial.println("Cannot control pin " + String(pin));
    return;
  }
  pinMode(pin, OUTPUT);
  digitalWrite(pin, state);
  Serial.println("Pin " + String(pin) + " set to " + (state == HIGH ? "HIGH" : "LOW"));
}

// Set PWM on a pin
void setPinPWM(int pin, int percent) {
  if (isForbiddenPin(pin)) {
    Serial.println("Cannot control pin " + String(pin));
    return;
  }
  int dutyCycle = map(percent, 0, 100, 0, 255);
  ledcAttachPin(pin, pin); // Use pin number as channel
  ledcSetup(pin, 5000, 8); // 5 kHz frequency, 8-bit resolution
  ledcWrite(pin, dutyCycle);
  Serial.println("Pin " + String(pin) + " PWM set to " + String(percent) + "%");
}

// Check if pin is forbidden
bool isForbiddenPin(int pin) {
  for (int i = 0; i < sizeof(FORBIDDEN_PINS) / sizeof(FORBIDDEN_PINS[0]); i++) {
    if (pin == FORBIDDEN_PINS[i]) {
      return true;
    }
  }
  return false;
}

// Set RGB LED color
void setLEDColor(int r, int g, int b) {
  currentR = r;
  currentG = g;
  currentB = b;
  analogWrite(LED_R_PIN, r);
  analogWrite(LED_G_PIN, g);
  analogWrite(LED_B_PIN, b);
  Serial.println("LED color set to R:" + String(r) + " G:" + String(g) + " B:" + String(b));
}

// Parse RGB values from string
bool parseRGB(String rgbStr, int& r, int& g, int& b) {
  int firstDash = rgbStr.indexOf('-');
  int secondDash = rgbStr.indexOf('-', firstDash + 1);

  if (firstDash == -1 || secondDash == -1) {
    return false;
  }

  r = rgbStr.substring(0, firstDash).toInt();
  g = rgbStr.substring(firstDash + 1, secondDash).toInt();
  b = rgbStr.substring(secondDash + 1).toInt();

  return (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255);
}

// Get RGB values from color name
bool getColorFromName(String colorName, int& r, int& g, int& b) {
  if (colorName == "red") {
    r = 255; g = 0; b = 0;
  } else if (colorName == "green") {
    r = 0; g = 255; b = 0;
  } else if (colorName == "blue") {
    r = 0; g = 0; b = 255;
  } else if (colorName == "yellow") {
    r = 255; g = 255; b = 0;
  } else if (colorName == "cyan") {
    r = 0; g = 255; b = 255;
  } else if (colorName == "magenta") {
    r = 255; g = 0; b = 255;
  } else if (colorName == "white") {
    r = 255; g = 255; b = 255;
  } else if (colorName == "purple") {
    r = 128; g = 0; b = 128;
  } else {
    return false;
  }
  return true;
}

// Lower brightness by a percentage
void lowerBrightness(int percent) {
  int factor = 100 - percent;
  int r = (currentR * factor) / 100;
  int g = (currentG * factor) / 100;
  int b = (currentB * factor) / 100;
  setLEDColor(r, g, b);
  Serial.println("Brightness lowered by " + String(percent) + "%");
}

Description:

  • Handles various commands to control GPIO pins and the RGB LED.
  • Avoids controlling pins 19 and 20.
  • Supports commands like:
    • turn on pin 4
    • pin 4 off
    • pin 5 50%
    • led turn on
    • led 255-0-255
    • set color blue
    • lower brightness by 50%

Instructions:

  1. Replace LED_R_PIN, LED_G_PIN, and LED_B_PIN with the actual GPIO pins connected to your RGB LED.
  2. Upload the code to your ESP32-S3.
  3. Run the Python script and enter commands to control the microcontroller.

Conclusion

By following this lesson, you've learned how to:

  • Establish a Wi-Fi connection between a Python script and an ESP32-S3 in Access Point mode.
  • Send and receive messages between the computer and microcontroller.
  • Measure and plot ping times to assess communication latency.
  • Control GPIO pins and the onboard RGB LED remotely using custom commands.

Feel free to expand upon this foundation to build more complex IoT applications and remote control systems.


Note: Ensure your computer is connected to the ESP32-S3's Wi-Fi network before running the Python scripts. Adjust the IP address and port if necessary.