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).

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
- Connect to WiFi: Join “R2D2 Astromech” network
- Enter Password: “C3POsucks”
- TCP Connection: Connect to 192.168.4.1:9750
- 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)

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.
