(Help needed) interacting with a Python script (WiFi)
Posted: Sun Aug 13, 2023 1:36 am
Hi,
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:
For a more detailed look at the code, here is the WiFi task code:
In my setup function, I make sure that the WiFi AP is initialized before creating any tasks, here is the code that I use:
In my Python project, I implemented a class that connects to the ESP32 and automatically re-opens the connection if it fails:
The code works briefly but from time to time it disconnects or is slow to send/receive data. How can I improve the stability and speed of this connection? Any hints or recommendations are highly welcome.
Thanks,
Alex Spataru
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:
Code: WiFi_Task.cpp Select all
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:
Code: Setup.cpp Select all
// 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:
Code: Client.py Select all
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