ESP as XBEE replacement

Estimated reading time: 8 min

Please read here: https://www.printed-droid.com/kb/astrocomms/ about flashing the ESP with a better version designed for the Astrocan(, but also usable for any ShadowMD setup.)

The Xbee is often used for controlling droids or exchange informations between arduinos.
But it’s a little expensive and sometimes hard to get.

But it’s possible to replace it with an ESP (tested so far: ESP32, ESP8266 Wemos D1, ESP8266-01).

All of them have been successfully tested

I recommend using an ESP32 (my favorite is the ESP32 D1 Mini – the middle one in the picture) as it offers the most power for an extra $ 1-2. Price is ~ 8USD on Amazon or ~ 4USD on Aliexpress

Programming the ESP with Arduino IDE

Programming the ESP is pretty simple. Only the ESP8266-01 needs an adapter and can be a little tricky.

First of all you have to install the used board within the arduino IDE (ESP32 or ESP8266).

Instructions for ESP32: https://randomnerdtutorials.com/installing-the-esp32-board-in-arduino-ide-windows-instructions/

Instructions for ESP8266: https://randomnerdtutorials.com/how-to-install-esp8266-board-arduino-ide/


After the board has been successfully installed, all you have to do is upload the appropriate sketch.
The SSID and password should be changed in the sketch.

This optimized sketch transforms an ESP8266 microcontroller into a wireless replacement for XBEE modules, specifically designed for Astromech droid projects (R2-D2, BB-8, etc.). It provides bidirectional communication between WiFi clients and serial devices like Astrocomms, Benduino, or Marcduino boards.

Core Functionality
  • Bidirectional Communication: Full WiFi ↔ Serial data transfer
  • Access Point Mode: Creates its own WiFi network
  • TCP Server: Listens on port 9750 for client connections
  • Smart Client Management: Handles connect/disconnect events gracefully
  • Filtered Display: Shows only meaningful commands, filters TCP overhead
Performance Optimizations
  • Non-blocking Operations: Maintains responsiveness
  • Minimal Memory Usage: Efficient string handling with F() macros
  • Low CPU Overhead: Optimized loops and timing
  • Stable Connections: Robust error handling and cleanup
Debug Features
  • Connection Status: Real-time connect/disconnect notifications
  • Waiting Status: 10-second updates until first client connects
  • Client Information: IP address and channel display
  • Command Visibility: Clean display of received commands

Network Configuration

Default Settings
SSID: R2D2 Astromech
Password: C3POsucks
IP Address: 192.168.4.1
TCP Port: 9750
Serial Baud: 9600
Customizable Parameters
const char* ssid = "R2D2 Astromech";     // WiFi network name
const char* pass = "C3POsucks";          // WiFi password
const uint16_t serverPort = 9750;        // TCP server port
const uint32_t serialBaud = 9600;        // Serial communication rate

Hardware Connection

ESP32/8266 to Astromech Board
ESP8266    →    Astromech Board
5V         →    5V (or 3.3V)
GND        →    GND
TX (GPIO1) →    RX
RX (GPIO3) →    TX

Serial Monitor Output

Startup Sequence
ESP8266 XBEE Replacement - Starting...
Version: 1.6 Optimized
Access Point started successfully
--- Network Configuration ---
SSID: R2D2 Astromech
Password: C3POsucks
AP IP address: 192.168.4.1
Server Port: 9750
MAC Address: XX:XX:XX:XX:XX:XX
--- End Configuration ---
TCP server started
Astromech ready
Waiting for client connection...
Connection Events
Waiting for connection... WiFi clients: 0 (no WiFi connection)
Waiting for connection... WiFi clients: 1 (WiFi connected, waiting for TCP)
TCP client connected
Client IP: 192.168.4.2, Channel: 1
TCP client disconnected
Command Display
#123        ← Received command from WiFi client
:SE00       ← Response from astromech board
#124        ← Next command
:SE01       ← Next response
Status Updates
Heartbeat - Uptime: 120s
Heartbeat - Uptime: 150s

Client Connection

Supported Clients
  • Smartphone Apps: Custom astromech control apps
  • Tablets: Touch-based control interfaces
  • Laptops: Development and testing tools
  • Other ESP32/Arduino: Peer-to-peer communication

Connection Process

  1. Connect to WiFi: Join “R2D2 Astromech” network
  2. Enter Password: “C3POsucks”
  3. TCP Connection: Connect to 192.168.4.1:9750
  4. Send Commands: Transmit astromech commands as strings

Troubleshooting

Common Issues

No Client Connection
  • Check WiFi: Verify network visibility
  • Password: Confirm correct password
  • Range: Move closer to ESP8266
  • Serial Monitor: Check “Waiting for connection” messages
Commands Not Working
  • Baud Rate: Verify 9600 baud on astromech board
  • Wiring: Check TX/RX connections
  • Board Compatibility: Confirm astromech board protocol
Frequent Disconnections
  • Power Supply: Ensure stable 5V/3.3V power
  • WiFi Interference: Change location or channel
  • Client Timeout: Check client-side connection handling

Debug Information

Monitor serial output for:

  • Connection status changes
  • Client IP addresses
  • Command flow
  • Error messages

Customization Options

Network Settings
// Custom IP configuration
IPAddress apIP(192, 168, 1, 100);      // Custom IP
IPAddress apGateway(192, 168, 1, 1);   // Gateway IP
IPAddress apSubnet(255, 255, 255, 0);  // Subnet mask
Timing Parameters
const unsigned long heartbeatInterval = 60000;     // 60 seconds
const unsigned long waitingCheckInterval = 5000;   // 5 seconds
Debug Levels
#define DEBUG_COMMANDS    // Show received commands
#define DEBUG_CONNECTION  // Show connection events
#define DEBUG_HEARTBEAT   // Show periodic status

Advanced Features

Multiple Client Support
  • Current version supports one active TCP client
  • WiFi can handle multiple connections simultaneously
  • TCP server serves first-come-first-served

Security Considerations

  • Change Default Password: Use stronger WiFi password
  • MAC Filtering: Implement client restrictions if needed
  • Encryption: WPA2 encryption enabled by default

Performance Monitoring

  • Uptime Tracking: Automatic uptime reporting
  • Connection Stability: Disconnect/reconnect monitoring
  • Data Flow: Real-time command visibility

Version History

v1.6 Optimized (2025/07/13)
  • Filtered command display
  • Optimized connection handling
  • Reduced serial output spam
  • Improved stability
v1.5 (2024/01)
  • Custom IP configuration
  • Enhanced client management
v1.4 (2023/08)
  • Memory optimization
  • Improved debug output
v1.3 (2021/10)
  • Initial release
  • Basic WiFi-Serial bridge

Disclaimer

Use at your own risk and without further support. Test range and reliability before deployment in your astromech droid.

Sketch for ESP32 (download at the bottom):

/*
ESP32 XBEE sketch v1.6 - Optimized Version
Date: 2025/07/13

www.printed-droid.com

This sketch makes it possible to use an ESP32 instead of the XBEE.
Optimized for bidirectional communication and better client management.

Use is at your own risk and without further support.
Please test the range and reliability before use.

More information is available at: https://www.printed-droid.com/kb/esp-as-xbee-replacement/

Change the SSID and password in the sketch!!!
The ESP IP is visible via the Arduino IDE serial monitor (default: 192.168.4.1)

Connection to Astrocomms, Benduino or Marcduino like the XBEE:
5V (or 3.3V)
GND
RX to TX
TX to RX
*/

#include <WiFi.h>

// Configuration - Change these values according to your needs
const char* ssid = "R2D2 Astromech";     // Name of the WiFi network created by the ESP32
const char* pass = "C3POsucks";          // WiFi password - replace with a more secure password!
const uint16_t serverPort = 9750;        // TCP server port
const uint32_t serialBaud = 9600;        // Serial communication baud rate

// Network configuration
IPAddress apIP(192, 168, 4, 1);          // Access Point IP address
IPAddress apGateway(192, 168, 4, 1);     // Gateway IP address
IPAddress apSubnet(255, 255, 255, 0);    // Subnet mask

// Global variables
WiFiServer server(serverPort);
WiFiClient client;
bool clientConnected = false;
bool firstClientConnected = false;  // Track if we ever had a client
unsigned long lastHeartbeat = 0;
unsigned long lastWaitingCheck = 0;
const unsigned long heartbeatInterval = 30000; // 30 seconds heartbeat
const unsigned long waitingCheckInterval = 10000; // Check every 10 seconds while waiting

// Function prototypes
void setupWiFi();
void handleClientConnection();
void handleDataTransfer();
void printNetworkInfo();
void sendHeartbeat();
void showConnectionDetails();
void checkWaitingStatus();

void setup() {
  // Initialize serial communication
  Serial.begin(serialBaud);
  delay(100);
  
  Serial.println();
  Serial.println(F("ESP32 XBEE Replacement - Starting..."));
  Serial.println(F("Version: 1.6 - www.printed-droid.com"));
  
  // Setup WiFi Access Point
  setupWiFi();
  
  // Start TCP server
  server.begin();
  Serial.println(F("TCP server started"));
  Serial.println(F("Astromech ready"));
  Serial.println(F("Waiting for client connection..."));
}

void loop() {
  // Handle client connections
  handleClientConnection();
  
  // Handle bidirectional data transfer
  if (clientConnected) {
    handleDataTransfer();
  }
  
  // Check waiting status (only until first client connects)
  if (!firstClientConnected) {
    checkWaitingStatus();
  }
  
  // Send periodic heartbeat
  sendHeartbeat();
  
  // Small delay to prevent excessive CPU usage
  delay(1);
}

void setupWiFi() {
  // Configure Access Point
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apGateway, apSubnet);
  
  // Start Access Point
  if (WiFi.softAP(ssid, pass)) {
    Serial.println(F("Access Point started successfully"));
  } else {
    Serial.println(F("Failed to start Access Point"));
    return;
  }
  
  // Print network information
  printNetworkInfo();
}

void handleClientConnection() {
  // Check if current client is still connected
  if (clientConnected && !client.connected()) {
    Serial.println(F("TCP client disconnected"));
    clientConnected = false;
    client.stop();
  }
  
  // Check for new client connections
  if (!clientConnected) {
    WiFiClient newClient = server.available();
    if (newClient) {
      // Stop any existing client connection
      if (client) {
        client.stop();
      }
      
      // Accept new client
      client = newClient;
      clientConnected = true;
      firstClientConnected = true;  // Mark that we had our first client
      
      Serial.println(F("TCP client connected"));
      showConnectionDetails();
    }
  }
}

void handleDataTransfer() {
  if (!client.connected()) {
    return;
  }
  
  // Transfer data from TCP client to Serial (WiFi -> Serial)
  if (client.available() > 0) {
    String receivedData = "";
    
    // Read all available data into string
    while (client.available() > 0) {
      char receivedByte = client.read();
      receivedData += receivedByte;
    }
    
    // Only print if we have visible/printable content
    bool hasVisibleContent = false;
    for (int i = 0; i < receivedData.length(); i++) {
      char c = receivedData.charAt(i);
      if (c >= 32 && c <= 126) {  // Printable ASCII characters
        hasVisibleContent = true;
        break;
      }
    }
    
    // Output data only if it contains visible content
    if (hasVisibleContent) {
      Serial.print(receivedData);
      Serial.println();  // Single newline after visible data
    }
  }
  
  // Transfer data from Serial to TCP client (Serial -> WiFi)
  while (Serial.available() > 0) {
    char serialByte = Serial.read();
    client.write(serialByte);
  }
}

void printNetworkInfo() {
  Serial.println(F("--- Network Configuration ---"));
  Serial.print(F("SSID: "));
  Serial.println(ssid);
  Serial.print(F("Password: "));
  Serial.println(pass);
  Serial.print(F("AP IP address: "));
  Serial.println(WiFi.softAPIP());
  Serial.print(F("Server Port: "));
  Serial.println(serverPort);
  Serial.print(F("MAC Address: "));
  Serial.println(WiFi.macAddress());
  Serial.println(F("--- End Configuration ---"));
}

void sendHeartbeat() {
  unsigned long currentTime = millis();
  
  // Handle millis() overflow
  if (currentTime < lastHeartbeat) {
    lastHeartbeat = currentTime;
  }
  
  // Send heartbeat message
  if (currentTime - lastHeartbeat >= heartbeatInterval) {
    Serial.print(F("Heartbeat - Uptime: "));
    Serial.print(currentTime / 1000);
    Serial.println(F("s"));
    
    lastHeartbeat = currentTime;
  }
}

void showConnectionDetails() {
  Serial.print(F("Client IP: "));
  Serial.print(client.remoteIP());
  Serial.print(F(", Channel: "));
  Serial.println(WiFi.channel());
}

void checkWaitingStatus() {
  unsigned long currentTime = millis();
  
  // Handle millis() overflow
  if (currentTime < lastWaitingCheck) {
    lastWaitingCheck = currentTime;
  }
  
  // Show waiting status every 10 seconds until first client connects
  if (currentTime - lastWaitingCheck >= waitingCheckInterval) {
    uint8_t wifiClients = WiFi.softAPgetStationNum();
    
    Serial.print(F("Waiting for connection... WiFi clients: "));
    Serial.print(wifiClients);
    if (wifiClients > 0) {
      Serial.println(F(" (WiFi connected, waiting for TCP)"));
    } else {
      Serial.println(F(" (no WiFi connection)"));
    }
    
    lastWaitingCheck = currentTime;
  }
}

ESP32 Board setting:

ESP8266 Board setting:

Sketch for ESP8266 (download at the bottom) :

/*
ESP8266 XBEE sketch v1.6 - Optimized Version
Date: 2025/07/13

www.printed-droid.com

This sketch makes it possible to use an ESP8266 instead of the XBEE.
Optimized for bidirectional communication and better client management.

Use is at your own risk and without further support.
Please test the range and reliability before use.

More information is available at: https://www.printed-droid.com/kb/esp-as-xbee-replacement/

Change the SSID and password in the sketch!!!
The ESP IP is visible via the Arduino IDE serial monitor (default: 192.168.4.1)

Connection to Astrocomms, Benduino or Marcduino like the XBEE:
5V
GND
RX to TX
TX to RX
*/

#include <ESP8266WiFi.h>

// Configuration - Change these values according to your needs
const char* ssid = "R2D2 Astromech";     // Name of the WiFi network created by the ESP8266
const char* pass = "C3POsucks";          // WiFi password - replace with a more secure password!
const uint16_t serverPort = 9750;        // TCP server port
const uint32_t serialBaud = 9600;        // Serial communication baud rate

// Network configuration
IPAddress apIP(192, 168, 4, 1);          // Access Point IP address
IPAddress apGateway(192, 168, 4, 1);     // Gateway IP address
IPAddress apSubnet(255, 255, 255, 0);    // Subnet mask

// Global variables
WiFiServer server(serverPort);
WiFiClient client;
bool clientConnected = false;
bool firstClientConnected = false;  // Track if we ever had a client
unsigned long lastHeartbeat = 0;
unsigned long lastWaitingCheck = 0;
const unsigned long heartbeatInterval = 30000; // 30 seconds heartbeat
const unsigned long waitingCheckInterval = 10000; // Check every 10 seconds while waiting

// Function prototypes
void setupWiFi();
void handleClientConnection();
void handleDataTransfer();
void printNetworkInfo();
void sendHeartbeat();
void showConnectionDetails();
void checkWaitingStatus();

void setup() {
  // Initialize serial communication
  Serial.begin(serialBaud);
  delay(100);
  
  Serial.println();
  Serial.println(F("ESP8266 XBEE Replacement - Starting..."));
  Serial.println(F("Version: 1.6 - www.printed-droid.com"));
  
  // Setup WiFi Access Point
  setupWiFi();
  
  // Start TCP server
  server.begin();
  Serial.println(F("TCP server started"));
  Serial.println(F("Astromech ready"));
  Serial.println(F("Waiting for client connection..."));
}

void loop() {
  // Handle client connections
  handleClientConnection();
  
  // Handle bidirectional data transfer
  if (clientConnected) {
    handleDataTransfer();
  }
  
  // Check waiting status (only until first client connects)
  if (!firstClientConnected) {
    checkWaitingStatus();
  }
  
  // Send periodic heartbeat
  sendHeartbeat();
  
  // Small delay to prevent excessive CPU usage
  delay(1);
}

void setupWiFi() {
  // Configure Access Point
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apGateway, apSubnet);
  
  // Start Access Point
  if (WiFi.softAP(ssid, pass)) {
    Serial.println(F("Access Point started successfully"));
  } else {
    Serial.println(F("Failed to start Access Point"));
    return;
  }
  
  // Print network information
  printNetworkInfo();
}

void handleClientConnection() {
  // Check if current client is still connected
  if (clientConnected && !client.connected()) {
    Serial.println(F("TCP client disconnected"));
    clientConnected = false;
    client.stop();
  }
  
  // Check for new client connections
  if (!clientConnected) {
    WiFiClient newClient = server.available();
    if (newClient) {
      // Stop any existing client connection
      if (client) {
        client.stop();
      }
      
      // Accept new client
      client = newClient;
      clientConnected = true;
      firstClientConnected = true;  // Mark that we had our first client
      
      Serial.println(F("TCP client connected"));
      showConnectionDetails();
    }
  }
}

void handleDataTransfer() {
  if (!client.connected()) {
    return;
  }
  
  // Transfer data from TCP client to Serial (WiFi -> Serial)
  if (client.available() > 0) {
    String receivedData = "";
    
    // Read all available data into string
    while (client.available() > 0) {
      char receivedByte = client.read();
      receivedData += receivedByte;
    }
    
    // Only print if we have visible/printable content
    bool hasVisibleContent = false;
    for (int i = 0; i < receivedData.length(); i++) {
      char c = receivedData.charAt(i);
      if (c >= 32 && c <= 126) {  // Printable ASCII characters
        hasVisibleContent = true;
        break;
      }
    }
    
    // Output data only if it contains visible content
    if (hasVisibleContent) {
      Serial.print(receivedData);
      Serial.println();  // Single newline after visible data
    }
  }
  
  // Transfer data from Serial to TCP client (Serial -> WiFi)
  while (Serial.available() > 0) {
    char serialByte = Serial.read();
    client.write(serialByte);
  }
}

void printNetworkInfo() {
  Serial.println(F("--- Network Configuration ---"));
  Serial.print(F("SSID: "));
  Serial.println(ssid);
  Serial.print(F("Password: "));
  Serial.println(pass);
  Serial.print(F("AP IP address: "));
  Serial.println(WiFi.softAPIP());
  Serial.print(F("Server Port: "));
  Serial.println(serverPort);
  Serial.print(F("MAC Address: "));
  Serial.println(WiFi.softAPmacAddress());
  Serial.println(F("--- End Configuration ---"));
}

void sendHeartbeat() {
  unsigned long currentTime = millis();
  
  // Handle millis() overflow
  if (currentTime < lastHeartbeat) {
    lastHeartbeat = currentTime;
  }
  
  // Send heartbeat message
  if (currentTime - lastHeartbeat >= heartbeatInterval) {
    Serial.print(F("Heartbeat - Uptime: "));
    Serial.print(currentTime / 1000);
    Serial.println(F("s"));
    
    lastHeartbeat = currentTime;
  }
}

void showConnectionDetails() {
  Serial.print(F("Client IP: "));
  Serial.print(client.remoteIP());
  Serial.print(F(", Channel: "));
  Serial.println(WiFi.channel());
}

void checkWaitingStatus() {
  unsigned long currentTime = millis();
  
  // Handle millis() overflow
  if (currentTime < lastWaitingCheck) {
    lastWaitingCheck = currentTime;
  }
  
  // Show waiting status every 10 seconds until first client connects
  if (currentTime - lastWaitingCheck >= waitingCheckInterval) {
    uint8_t wifiClients = WiFi.softAPgetStationNum();
    
    Serial.print(F("Waiting for connection... WiFi clients: "));
    Serial.print(wifiClients);
    if (wifiClients > 0) {
      Serial.println(F(" (WiFi connected, waiting for TCP)"));
    } else {
      Serial.println(F(" (no WiFi connection)"));
    }
    
    lastWaitingCheck = currentTime;
  }
}

ESP8266-01 Board setting:

The ESP8266-01 needs the Builtin LED set to 1 instead of 2!

For instructions how to uplad a sketch to the ESP8266-01 look here: https://www.instructables.com/Getting-Started-With-Esp-8266-Esp-01-With-Arduino-/


Connection:

Then the ESP is connected to the Astrocomms, Benduino or Marcduino like the XBEE.
5V
GND
RX to TX
TX to RX

The exception is the ESP8266-01. Here the power supply must be via 3V3 and this must also be connected to the Enabled pin (EN)

Sometimes RX/TX is swapped on the ESP32 mini – don’t know why, but it’s possible!

Additional Info:
In the R2 Touch app you need to make two changes in the settings:
Change the Receiver IP to 192.168.4.1 (maybe 192.168.1.10 or the IP defined within the sketch)
Change the Receiver Port to 9750 (or the Port defined within the sketch)

Adding an external antenna:

It’s possible to add an external antenna to an ESP. But be careful what you do.

Tags:
Was this article helpful?
Dislike 0 11 of 11 found this article helpful.
Views: 4264
Previous: Domelift Controller