I'm building a toy car robot with an ESP-WROOM-32 MCU, using ultrasonic distance sensors and a DQN model. I've implemented a system where the ESP32 receives an "action" from a Python script, performs it, and then sends back the robot's "state" to the script. My problem lies in the TCP socket communication; it's too slow and often disconnects, hindering the training process.
Here's an outline of my setup:
- ESP32: Handles WiFi communications on core 1, other tasks like sensor reading on core 0. It initializes a WiFi server and listens for incoming connections to receive actions and send states.
Python Client: Sends commands to the robot and tries to receive data back. I've included code to handle reconnections, but it's not efficient and relies heavily on time delays.
For a more detailed look at the code, here is the WiFi task code:
- void task_remote_operation(void *pv_parameters) {
- // Initialize variables for implementing a simple watchdog
- uint64_t watchdog_time = millis();
- // Initialize TCP server
- WiFiServer server(SRVR_PORT);
- server.begin();
- // Infinite loop
- while (true) {
- // Listen for incoming connections
- WiFiClient client = server.available();
- if (client) {
- // Reset watchdog
- watchdog_time = millis();
- // Disable combining different messages in the same packet
- client.setNoDelay(true);
- // Inhibit neural network from controling the robot
- if (!STATE.teleop) {
- if (xSemaphoreTake(MUTEX, portMAX_DELAY) == pdTRUE) {
- STATE.teleop = true;
- xSemaphoreGive(MUTEX);
- }
- }
- // Enter client/robot communication loop
- while (client.connected()) {
- if (client.available()) {
- // Parse incoming action command
- String action_str = client.readStringUntil('\n');
- action_str.trim();
- parse_action(&action_str);
- // Send acknowledgement to training process
- client.print("ACK");
- // Send state string
- String state_str = encode_state();
- state_str.trim();
- client.println(state_str);
- // Stop watchdog from reseting the motor outputs
- watchdog_time = millis();
- }
- // Stop the robot if training computer is disconnected
- if (millis() - watchdog_time > 3000) {
- if (xSemaphoreTake(MUTEX, portMAX_DELAY) == pdTRUE) {
- reset_state();
- xSemaphoreGive(MUTEX);
- }
- watchdog_time = millis();
- }
- // Wait a little
- vTaskDelay(100 / portTICK_PERIOD_MS);
- }
- // Connection with client stopped, reset state
- if (xSemaphoreTake(MUTEX, portMAX_DELAY) == pdTRUE) {
- reset_state();
- xSemaphoreGive(MUTEX);
- }
- // Disonnect from the client if required
- client.stop();
- }
- // Wait a little
- else
- vTaskDelay(200 / portTICK_PERIOD_MS);
- }
- }
In my setup function, I make sure that the WiFi AP is initialized before creating any tasks, here is the code that I use:
- // Create Wi-Fi access point
- if (!WiFi.softAP(WIFI_SSID, WIFI_PASS)) {
- while (true)
- delay(1);
- }
In my Python project, I implemented a class that connects to the ESP32 and automatically re-opens the connection if it fails:
- import time
- import socket
- import numpy as np
- class Client:
- """
- A client class to handle real-time interaction with a microcontroller via
- TCP connection. The class sends actions and receives states, ensuring
- efficient communication with the physical robot's actions during the
- training phase.
- Attributes:
- host (str): The IP address of the microcontroller.
- port (int): The port number for the TCP connection.
- client (socket): The socket object used to manage the TCP connection.
- """
- def __init__(self, host='192.168.4.1', port=80):
- """
- Initializes the client with given host and port.
- Args:
- host (str, optional): The IP address of the microcontroller.
- port (int, optional): The port number for the TCP connection.
- """
- self.host = host
- self.port = port
- self.client = self.connect()
- def connect(self):
- """
- Creates and returns a socket object to manage the TCP connection.
- Returns:
- socket: The connected socket object.
- """
- client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- client.connect((self.host, self.port))
- client.settimeout(5)
- return client
- def close(self):
- """Closes the TCP connection."""
- self.client.close()
- def reconnect(self):
- """Closes and reestablishes the TCP connection."""
- self.client.close()
- time.sleep(1)
- self.client = self.connect()
- def write(self, command, silent):
- """
- Sends a command to the microcontroller, then waits for acknowledgment
- and finally receives data.
- Args:
- command (str): The command to send to the microcontroller.
- silent (bool): If False, prints debug information.
- Returns:
- str: The data received from the microcontroller.
- """
- data = ''
- retries = 5
- while retries > 0 and data == '':
- try:
- self.client.sendall(command.encode())
- acknowledgment = self.client.recv(32).decode().strip()
- if acknowledgment != 'ACK':
- raise ConnectionError('Invalid acknowledgment from ESP32')
- raw = self.client.recv(1024).decode()
- data = raw.replace('\r', '').replace('\n', '')
- break
- except (socket.timeout,
- BrokenPipeError,
- ConnectionResetError,
- ConnectionError):
- retries -= 1
- self.reconnect()
- return data
- def send_action(self, action, silent=True):
- """
- Transmits an action to the microcontroller and receives the
- corresponding state from the robot.
- Args:
- action (np.array): The action to transmit.
- silent (bool, optional): If False, prints debug information.
- Returns:
- np.array: The state received from the microcontroller.
- """
- frame = ','.join(map(str, action.flatten())) + '\n'
- response = self.write(frame, silent)
- if response == '':
- return self.send_action(action, silent)
- if not silent:
- print('[TX] ', end='')
- print(action)
- state = np.array(list(map(float, response.split(','))))
- if not silent:
- print('[RX] ', end='')
- print(state)
- print('')
- return state
Thanks,
Alex Spataru