Fab Academy Communications MicroPython
1. Convert your esp to python
2. I2C
MicroPython version in Thonny
These two scripts follow the same idea as the Arduino example, but in MicroPython.
In this version:
- the controller board writes a short text into the target memory
- the target board updates a reply when that write finishes
- the controller then reads the reply from another memory area
This approach is a good fit for MicroPython because it uses machine.I2C on the controller side and machine.I2CTarget on the target side.
MicroPython I2C controller code
from machine import Pin, I2C
import time
# Set the XIAO ESP32C6 I2C pins.
SDA_PIN = 22
SCL_PIN = 23
# Set the address of the target board.
I2C_ADDRESS = 0x55
# Use register 0 for the incoming message and register 32 for the reply.
RX_REGISTER = 0
TX_REGISTER = 32
# Start I2C controller mode on bus 0.
i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=100000)
# Keep track of how many messages have been sent.
message_count = 0
while True:
# Build the message that will be sent to the target board.
outgoing_message = "Hello from controller #{}".format(message_count)
outgoing_bytes = outgoing_message.encode()
# Write the outgoing message into the target memory starting at register 0.
i2c.writeto_mem(I2C_ADDRESS, RX_REGISTER, outgoing_bytes)
# Print what was sent.
print("Sent:", outgoing_message)
# Wait a short moment so the target can prepare the reply.
time.sleep_ms(200)
# Read up to 32 bytes from the reply area.
incoming_bytes = i2c.readfrom_mem(I2C_ADDRESS, TX_REGISTER, 32)
# Remove empty bytes and decode the reply as text.
incoming_message = incoming_bytes.split(b"\x00", 1)[0].decode()
# Print the reply from the target board.
print("Reply:", incoming_message)
print()
# Increase the message number for the next loop.
message_count += 1
# Wait before sending the next message.
time.sleep(2)
MicroPython I2C target code
from machine import Pin, I2CTarget
import time
# Set the XIAO ESP32C6 I2C pins.
SDA_PIN = 22
SCL_PIN = 23
# Set the target address.
I2C_ADDRESS = 0x55
# Create a small memory map.
# Bytes 0 to 31 store what the controller wrote.
# Bytes 32 to 63 store the reply that the controller will read back.
mem = bytearray(64)
RX_START = 0
RX_LENGTH = 32
TX_START = 32
TX_LENGTH = 32
def write_text_to_region(text, start, length):
# Clear the selected region first.
for i in range(length):
mem[start + i] = 0
# Copy the encoded text into the region, leaving room for a zero byte.
data = text.encode()[: length - 1]
mem[start : start + len(data)] = data
def read_text_from_region(start, length):
# Read raw bytes from the selected region.
data = bytes(mem[start : start + length])
# Stop at the first zero byte so the decoded text is clean.
data = data.split(b"\x00", 1)[0]
return data.decode()
def on_i2c_event(i2c_target):
# Check which I2C event happened.
flags = i2c_target.irq().flags()
# When the controller finishes writing, read the new text and update the reply.
if flags & I2CTarget.IRQ_END_WRITE:
incoming_message = read_text_from_region(RX_START, RX_LENGTH)
print("Received:", incoming_message)
reply = "Target heard: {}".format(incoming_message)
write_text_to_region(reply, TX_START, TX_LENGTH)
print("Prepared reply:", reply)
# Write an initial reply so the controller has something to read at startup.
write_text_to_region("No message yet", TX_START, TX_LENGTH)
# Start I2C target mode.
# If your firmware reports an error here, try changing the bus id from 0 to 1.
i2c_target = I2CTarget(0, I2C_ADDRESS, mem=mem, sda=Pin(SDA_PIN), scl=Pin(SCL_PIN))
# Register an IRQ callback so the reply is updated after each write.
i2c_target.irq(on_i2c_event)
print("I2C target ready")
while True:
# Keep the script alive.
time.sleep_ms(100)
How to modify the MicroPython I2C version
You can change this version by editing:
I2C_ADDRESSif you need another address- the text written by the controller
- the way the target builds its reply
- the register layout if you want to store more than one value
A useful next step is to replace the text with small packets such as:
- one byte for an LED state
- two bytes for a sensor value
- a short block of bytes for commands and measurements
How to modify this code
To turn this into your own project, change:
I2C_ADDRESSif you need a different slave address- the outgoing message into sensor values, commands, or structured text
- the slave response into processed data or board status
- the timing in
delay(...)if you want faster or slower updates
You can also replace text messages with numeric packets. For example:
- send one byte for an LED state
- send two bytes for a sensor value
- send a short packet with a command ID and data bytes
3. Wi-Fi HTTP
MicroPython version in Thonny
This version uses network.WLAN to connect to Wi-Fi and socket to handle HTTP requests.
MicroPython Wi-Fi HTTP server code
import network
import socket
import time
from machine import Pin
# Replace these with your Wi-Fi network credentials.
WIFI_SSID = "YOUR_WIFI_NAME"
WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"
# Use the built-in LED on the XIAO ESP32C6.
LED_PIN = 15
led = Pin(LED_PIN, Pin.OUT)
led.value(0)
# Keep track of the LED state.
led_state = False
def connect_wifi():
# Start the station interface and connect to the Wi-Fi network.
wlan = network.WLAN(network.WLAN.IF_STA)
wlan.active(True)
if not wlan.isconnected():
print("Connecting to Wi-Fi...")
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
while not wlan.isconnected():
time.sleep_ms(500)
print(".", end="")
print()
print("Wi-Fi connected")
print("IP address:", wlan.ifconfig()[0])
return wlan
def build_html():
# Build the HTML page shown in the browser.
html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
html += "<title>XIAO ESP32C6 HTTP</title></head><body>"
html += "<h1>XIAO ESP32C6 HTTP Server</h1>"
html += "<p>LED state: {}</p>".format("ON" if led_state else "OFF")
html += "<p><a href='/led/on'>Turn LED ON</a></p>"
html += "<p><a href='/led/off'>Turn LED OFF</a></p>"
html += "<p><a href='/data'>Read JSON data</a></p>"
html += "</body></html>"
return html
def build_json():
# Build a simple JSON response.
json_text = "{"
json_text += '\"board\":\"XIAO ESP32C6\",'
json_text += '\"uptime_ms\":{},'.format(time.ticks_ms())
json_text += '\"led\":{}'.format("true" if led_state else "false")
json_text += "}"
return json_text
def send_response(client, status, content_type, body):
# Send an HTTP response header and body.
client.send("HTTP/1.1 {}\r\n".format(status).encode())
client.send("Content-Type: {}\r\n".format(content_type).encode())
client.send(b"Connection: close\r\n\r\n")
client.send(body.encode())
# Connect to Wi-Fi first.
wlan = connect_wifi()
# Open a socket on port 80.
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
server = socket.socket()
server.bind(addr)
server.listen(1)
print("HTTP server listening on", wlan.ifconfig()[0])
while True:
# Wait for a browser or another client to connect.
client, client_addr = server.accept()
print("Client connected from", client_addr)
try:
# Read the HTTP request.
request = client.recv(1024)
request_text = request.decode()
print(request_text)
# Extract the requested path from the first line.
request_line = request_text.split("\r\n")[0]
path = request_line.split(" ")[1]
# Decide what to return based on the path.
if path == "/":
send_response(client, "200 OK", "text/html", build_html())
elif path == "/data":
send_response(client, "200 OK", "application/json", build_json())
elif path == "/led/on":
led_state = True
led.value(1)
send_response(client, "200 OK", "text/plain", "LED is now ON")
elif path == "/led/off":
led_state = False
led.value(0)
send_response(client, "200 OK", "text/plain", "LED is now OFF")
else:
send_response(client, "404 Not Found", "text/plain", "Route not found")
except Exception as error:
print("Request error:", error)
finally:
# Always close the client socket after one request.
client.close()
How to modify the MicroPython HTTP version
You can adapt this version by changing:
- the HTML in
build_html() - the JSON content in
build_json() - the routes handled inside the main loop
- the LED action into another output or device function
How to modify this code
To make this your own:
- change
handleRoot()to create your own HTML page - change
handleData()to send your own sensor data - add new routes such as
/temperature,/motor/start, or/status - replace the LED control with relays, motors, or other outputs
- add JavaScript later if you want the page to update without refreshing
If you want the board to be an HTTP client instead of a server, you can later replace WebServer with HTTPClient and send requests to another server.
Extension: HTTP communication between two boards on the same network
Two boards can communicate through HTTP when both are connected to the same Wi-Fi network and one board knows the IP address of the other.
In this setup:
- Board A works as the HTTP server
- Board B works as the HTTP client
- Board B sends requests such as
GET /sensororGET /led/onto Board A - Board A replies with text, JSON, or another response format
What you need
- 2 XIAO ESP32C6 boards
- both boards connected to the same 2.4 GHz Wi-Fi network
- the IP address of the server board
- Arduino libraries:
WiFi.hWebServer.hon the server boardHTTPClient.hon the client board
Steps to run
- Upload the server sketch to Board A.
- Open the Serial Monitor and note the IP address printed by Board A.
- Put that IP address into the client sketch on Board B.
- Upload the client sketch to Board B.
- Board B sends an HTTP request to Board A at a regular interval.
- Board A answers with a text message that the client prints in the Serial Monitor.
Board A: HTTP server code
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
// Replace these with your Wi-Fi network credentials.
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// Create a web server on port 80.
WebServer server(80);
// Use the built-in LED so the client can control something visible.
const int LED_PIN = LED_BUILTIN;
// Keep track of the LED state.
bool ledState = false;
// This route returns a short text message.
void handleHello() {
server.send(200, "text/plain", "Hello from Board A");
}
// This route returns a small JSON object.
void handleSensor() {
String json = "{";
json += "\"board\":\"Board A\",";
json += "\"uptime_ms\":" + String(millis()) + ",";
json += "\"led\":" + String(ledState ? "true" : "false");
json += "}";
server.send(200, "application/json", json);
}
// This route turns the LED on.
void handleLedOn() {
ledState = true;
digitalWrite(LED_PIN, HIGH);
server.send(200, "text/plain", "LED ON on Board A");
}
// This route turns the LED off.
void handleLedOff() {
ledState = false;
digitalWrite(LED_PIN, LOW);
server.send(200, "text/plain", "LED OFF on Board A");
}
// This route handles unknown paths.
void handleNotFound() {
server.send(404, "text/plain", "Route not found");
}
void setup() {
Serial.begin(115200);
delay(1000);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Connect the board to the Wi-Fi network.
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("Board A connected to Wi-Fi");
Serial.print("Board A IP address: ");
Serial.println(WiFi.localIP());
// Register the routes that the other board can call.
server.on("/hello", handleHello);
server.on("/sensor", handleSensor);
server.on("/led/on", handleLedOn);
server.on("/led/off", handleLedOff);
server.onNotFound(handleNotFound);
// Start the HTTP server.
server.begin();
Serial.println("Board A HTTP server started");
}
void loop() {
// Listen for incoming HTTP requests from Board B.
server.handleClient();
}
Board B: HTTP client code
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
// Replace these with your Wi-Fi network credentials.
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// Replace this with the IP address printed by Board A.
const char* SERVER_IP = "192.168.1.50";
// Change the request interval if you want faster or slower polling.
unsigned long lastRequestTime = 0;
const unsigned long requestInterval = 5000;
void setup() {
Serial.begin(115200);
delay(1000);
// Connect this board to the same Wi-Fi network.
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("Board B connected to Wi-Fi");
Serial.print("Board B IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
// Send a request every few seconds.
if (millis() - lastRequestTime >= requestInterval) {
lastRequestTime = millis();
// Only send the request if Wi-Fi is still connected.
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
// Build the URL for the route exposed by Board A.
String url = "http://" + String(SERVER_IP) + "/sensor";
Serial.print("Requesting URL: ");
Serial.println(url);
// Open the HTTP connection.
http.begin(url);
// Send a GET request.
int httpResponseCode = http.GET();
// Check whether the request succeeded.
if (httpResponseCode > 0) {
Serial.print("HTTP response code: ");
Serial.println(httpResponseCode);
// Read the text returned by the server.
String payload = http.getString();
Serial.println("Response payload:");
Serial.println(payload);
} else {
Serial.print("HTTP request failed, error: ");
Serial.println(httpResponseCode);
}
// Close the HTTP connection.
http.end();
} else {
Serial.println("Wi-Fi disconnected, request not sent");
}
}
}
MicroPython version in Thonny
You can also run the board-to-board HTTP example in MicroPython.
Board A: MicroPython HTTP server code
import network
import socket
import time
from machine import Pin
# Replace these with your Wi-Fi network credentials.
WIFI_SSID = "YOUR_WIFI_NAME"
WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"
# Use the built-in LED on the XIAO ESP32C6.
LED_PIN = 15
led = Pin(LED_PIN, Pin.OUT)
led.value(0)
led_state = False
def connect_wifi():
# Connect the board to Wi-Fi.
wlan = network.WLAN(network.WLAN.IF_STA)
wlan.active(True)
if not wlan.isconnected():
print("Connecting to Wi-Fi...")
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
while not wlan.isconnected():
time.sleep_ms(500)
print(".", end="")
print()
print("Board A IP address:", wlan.ifconfig()[0])
return wlan
def send_response(client, status, content_type, body):
# Send a basic HTTP response.
client.send("HTTP/1.1 {}\r\n".format(status).encode())
client.send("Content-Type: {}\r\n".format(content_type).encode())
client.send(b"Connection: close\r\n\r\n")
client.send(body.encode())
# Connect to Wi-Fi and start listening for HTTP requests.
wlan = connect_wifi()
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
server = socket.socket()
server.bind(addr)
server.listen(1)
print("Board A HTTP server ready")
while True:
# Wait for the client board to connect.
client, client_addr = server.accept()
print("Request from", client_addr)
try:
# Read and decode the incoming HTTP request.
request = client.recv(1024)
request_text = request.decode()
request_line = request_text.split("\r\n")[0]
path = request_line.split(" ")[1]
# Respond according to the requested route.
if path == "/hello":
send_response(client, "200 OK", "text/plain", "Hello from Board A")
elif path == "/sensor":
payload = "{"
payload += '\"board\":\"Board A\",'
payload += '\"uptime_ms\":{},'.format(time.ticks_ms())
payload += '\"led\":{}'.format("true" if led_state else "false")
payload += "}"
send_response(client, "200 OK", "application/json", payload)
elif path == "/led/on":
led_state = True
led.value(1)
send_response(client, "200 OK", "text/plain", "LED ON on Board A")
elif path == "/led/off":
led_state = False
led.value(0)
send_response(client, "200 OK", "text/plain", "LED OFF on Board A")
else:
send_response(client, "404 Not Found", "text/plain", "Route not found")
except Exception as error:
print("Server error:", error)
finally:
# Close the socket for this request.
client.close()
Board B: MicroPython HTTP client code
import network
import socket
import time
# Replace these with your Wi-Fi network credentials.
WIFI_SSID = "YOUR_WIFI_NAME"
WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"
# Replace this with the IP address printed by Board A.
SERVER_IP = "192.168.1.50"
# Change the route if you want to call another server endpoint.
SERVER_PATH = "/sensor"
# Set how often the board sends a request.
REQUEST_INTERVAL_MS = 5000
def connect_wifi():
# Connect this board to the same Wi-Fi network.
wlan = network.WLAN(network.WLAN.IF_STA)
wlan.active(True)
if not wlan.isconnected():
print("Connecting to Wi-Fi...")
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
while not wlan.isconnected():
time.sleep_ms(500)
print(".", end="")
print()
print("Board B IP address:", wlan.ifconfig()[0])
return wlan
# Connect to Wi-Fi.
wlan = connect_wifi()
last_request_time = time.ticks_ms()
while True:
# Send a request at the selected interval.
if time.ticks_diff(time.ticks_ms(), last_request_time) >= REQUEST_INTERVAL_MS:
last_request_time = time.ticks_ms()
try:
# Resolve the server IP and open a socket.
addr = socket.getaddrinfo(SERVER_IP, 80)[0][-1]
client = socket.socket()
client.connect(addr)
# Build and send a simple HTTP GET request.
request = "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n".format(
SERVER_PATH, SERVER_IP
)
client.send(request.encode())
# Read the complete HTTP response.
response = b""
while True:
data = client.recv(256)
if not data:
break
response += data
client.close()
# Split the HTTP header from the body and print the body only.
if b"\r\n\r\n" in response:
body = response.split(b"\r\n\r\n", 1)[1]
else:
body = response
print("Response body:")
print(body.decode())
print()
except Exception as error:
print("Client error:", error)
time.sleep_ms(100)
How to modify the MicroPython extension
You can change this version by editing:
SERVER_PATHon Board B to call another route- the payload returned by Board A
- the request interval on Board B
- the action on Board A when a route is called
How to modify this extension
- change
"/sensor"to another route such as"/hello","/led/on", or"/led/off" - make the server return plain text, JSON, or HTML
- connect a button on Board B so a request is sent only when the button is pressed
- expose a sensor value on Board A and read it from Board B
- send commands from Board B to Board A, such as turning an output on or off
- replace the fixed
SERVER_IPwith a static IP configuration if the server address changes too often
Limitations
- HTTP is heavier than I2C or ESP-NOW
- one board usually waits for the other board to request data
- if the server IP address changes, the client code must be updated unless a static IP or another discovery method is used
- this is best for simple request and response communication, not for very fast real-time exchange
4. Wi-Fi MQTT
MicroPython version in Thonny
This version uses the MicroPython network interface together with umqtt.simple.
If Thonny reports ImportError: no module named umqtt.simple, copy that module to the board first or install it in your MicroPython environment.
MicroPython Wi-Fi MQTT code
import network
import time
from machine import Pin
from umqtt.simple import MQTTClient
# Replace these with your Wi-Fi credentials.
WIFI_SSID = "YOUR_WIFI_NAME"
WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"
# Replace these with your MQTT broker settings.
MQTT_BROKER = "192.168.1.100"
MQTT_PORT = 1883
MQTT_CLIENT_ID = b"xiao_esp32c6_client"
# Define the topics used by this board.
MQTT_PUBLISH_TOPIC = b"fabacademy/xiao/data"
MQTT_SUBSCRIBE_TOPIC = b"fabacademy/xiao/command"
# Use the built-in LED as a visible output.
LED_PIN = 15
led = Pin(LED_PIN, Pin.OUT)
led.value(0)
# Publish once every 5 seconds.
PUBLISH_INTERVAL_MS = 5000
def connect_wifi():
# Connect the board to Wi-Fi.
wlan = network.WLAN(network.WLAN.IF_STA)
wlan.active(True)
if not wlan.isconnected():
print("Connecting to Wi-Fi...")
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
while not wlan.isconnected():
time.sleep_ms(500)
print(".", end="")
print()
print("Wi-Fi connected")
print("IP address:", wlan.ifconfig()[0])
return wlan
def mqtt_callback(topic, message):
# Decode the incoming topic and payload.
topic_text = topic.decode()
message_text = message.decode()
print("MQTT message received on topic:", topic_text)
print("Payload:", message_text)
# React to simple text commands.
if message_text == "on":
led.value(1)
print("LED turned ON")
elif message_text == "off":
led.value(0)
print("LED turned OFF")
def connect_mqtt():
# Create the MQTT client and connect to the broker.
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER, port=MQTT_PORT)
client.set_callback(mqtt_callback)
client.connect()
client.subscribe(MQTT_SUBSCRIBE_TOPIC)
print("Connected to MQTT broker")
print("Subscribed to:", MQTT_SUBSCRIBE_TOPIC.decode())
return client
# Connect to Wi-Fi first.
wlan = connect_wifi()
client = connect_mqtt()
last_publish_time = time.ticks_ms()
while True:
try:
# Check whether a new subscribed message has arrived.
client.check_msg()
# Publish new data every few seconds.
if time.ticks_diff(time.ticks_ms(), last_publish_time) >= PUBLISH_INTERVAL_MS:
last_publish_time = time.ticks_ms()
payload = "{{\"uptime_ms\":{},\"wifi_rssi\":{}}}".format(
time.ticks_ms(), wlan.status("rssi")
)
client.publish(MQTT_PUBLISH_TOPIC, payload.encode())
print("Published to", MQTT_PUBLISH_TOPIC.decode(), ":", payload)
time.sleep_ms(100)
except OSError as error:
# Reconnect if Wi-Fi or MQTT drops.
print("MQTT error:", error)
time.sleep(2)
wlan = connect_wifi()
client = connect_mqtt()
How to modify the MicroPython MQTT version
You can adapt this version by changing:
MQTT_BROKERandMQTT_PORT- the publish and subscribe topics
- the action inside
mqtt_callback() - the JSON payload that is published
How to modify this code
You can adapt this template by changing:
MQTT_BROKERandMQTT_PORTto your broker- the publish topic and subscribe topic to match your project
- the callback so it reacts to more commands such as
blink,speed:120, orservo:45 - the published payload so it sends real sensor data instead of uptime and RSSI
A useful pattern is:
- one topic for sensor data
- one topic for commands
- one topic for status
For example:
fabacademy/team1/sensorfabacademy/team1/cmdfabacademy/team1/status
5. ESP-NOW
MicroPython version in Thonny
MicroPython on ESP32 includes an espnow module, so you can build the same direct board-to-board communication pattern in Thonny.
MicroPython ESP-NOW sender code
import network
import espnow
import time
import binascii
# Replace this with the MAC address of the receiver board.
PEER = b"\x24\x6f\x28\xaa\xbb\xcc"
# Start the station interface because ESP-NOW needs an active Wi-Fi interface.
sta = network.WLAN(network.WLAN.IF_STA)
sta.active(True)
# Print this board MAC address so you can identify it.
print("Sender MAC:", binascii.hexlify(sta.config("mac"), b":").decode())
# Start ESP-NOW and register the receiver board as a peer.
e = espnow.ESPNow()
e.active(True)
e.add_peer(PEER)
counter = 0
while True:
# Build a simple text packet.
# You can replace this with your own values later.
led_command = 1 if counter % 2 == 0 else 0
payload = "{},{},{}".format(counter, counter * 0.1, led_command)
# Send the payload to the receiver board.
e.send(PEER, payload)
print("Sent:", payload)
# Increase the counter for the next packet.
counter += 1
# Wait before sending the next message.
time.sleep(1)
MicroPython ESP-NOW receiver code
import network
import espnow
import time
import binascii
from machine import Pin
# Use the built-in LED as a visible output.
LED_PIN = 15
led = Pin(LED_PIN, Pin.OUT)
led.value(0)
# Start the station interface because ESP-NOW needs an active Wi-Fi interface.
sta = network.WLAN(network.WLAN.IF_STA)
sta.active(True)
# Print this board MAC address so the sender can use it.
print("Receiver MAC:", binascii.hexlify(sta.config("mac"), b":").decode())
# Start ESP-NOW receiving.
e = espnow.ESPNow()
e.active(True)
while True:
# Wait for a new packet.
host, msg = e.recv()
if msg:
# Convert the incoming bytes to text.
text = msg.decode()
print("Received from", binascii.hexlify(host, b":").decode())
print("Raw payload:", text)
# Split the text packet into its parts.
parts = text.split(",")
if len(parts) == 3:
counter = int(parts[0])
value = float(parts[1])
led_command = int(parts[2])
print("Counter:", counter)
print("Value:", value)
print("LED command:", led_command)
# Apply the command to the built-in LED.
led.value(led_command)
print()
time.sleep_ms(10)
How to modify the MicroPython ESP-NOW version
You can change this version by editing:
PEERin the sender script- the text packet format
- the values packed into the outgoing message
- the action taken by the receiver after parsing the message
How to modify this code
This pattern becomes much more useful if you replace the example struct with your own project data. For example:
- button states
- joystick values
- sensor readings
- machine status flags
- short commands for another board
To change the project, modify:
- the struct fields on both sender and receiver
- the receiver MAC address in the sender sketch
- the action on the receiver, such as controlling motors, relays, or LEDs
If you need two-way communication, make each board both a sender and a receiver.
6. BLE
MicroPython BLE server code
import bluetooth
import time
from machine import Pin
from micropython import const
# BLE event codes used by this example.
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
# Use the built-in LED as a visible output.
LED_PIN = 15
led = Pin(LED_PIN, Pin.OUT)
led.value(0)
# Define the same service and characteristic UUIDs used in the Arduino example.
SERVICE_UUID = bluetooth.UUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b")
CHARACTERISTIC_UUID = bluetooth.UUID("beb5483e-36e1-4688-b7f5-ea07361b26a8")
# Build a BLE service with one characteristic that can be read, written, and notified.
BLE_SERVICE = (
SERVICE_UUID,
(
(CHARACTERISTIC_UUID, bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY),
),
)
# Create and activate the BLE object.
ble = bluetooth.BLE()
ble.active(True)
# Register the BLE service and get the handle of the characteristic value.
((characteristic_handle,),) = ble.gatts_register_services((BLE_SERVICE,))
# Allow writes longer than the default 20 bytes.
ble.gatts_set_buffer(characteristic_handle, 100)
# Store an initial value that the client can read.
ble.gatts_write(characteristic_handle, b"Hello from XIAO ESP32C6")
# Keep track of connected clients.
connections = set()
def advertising_payload(name):
# Create a minimal BLE advertising payload with the device name.
name_bytes = name.encode()
return b"\x02\x01\x06" + bytes((len(name_bytes) + 1, 0x09)) + name_bytes
def advertise():
# Start BLE advertising so a phone or computer can find the board.
ble.gap_advertise(100000, adv_data=advertising_payload("XIAO_ESP32C6_BLE"))
print("BLE advertising started")
def bt_irq(event, data):
# Handle BLE connect, disconnect, and write events.
if event == _IRQ_CENTRAL_CONNECT:
conn_handle, addr_type, addr = data
connections.add(conn_handle)
print("Client connected")
elif event == _IRQ_CENTRAL_DISCONNECT:
conn_handle, addr_type, addr = data
connections.discard(conn_handle)
print("Client disconnected")
advertise()
elif event == _IRQ_GATTS_WRITE:
conn_handle, attr_handle = data
# Only react when the writable characteristic is updated.
if attr_handle == characteristic_handle:
value = ble.gatts_read(characteristic_handle).decode().strip()
print("BLE received:", value)
# React to simple text commands.
if value == "on":
led.value(1)
print("LED turned ON")
elif value == "off":
led.value(0)
print("LED turned OFF")
# Write the latest value back so the client can read it.
ble.gatts_write(characteristic_handle, value.encode())
# Notify all connected clients that the value changed.
for conn in connections:
ble.gatts_notify(conn, characteristic_handle)
# Register the IRQ callback and start advertising.
ble.irq(bt_irq)
advertise()
while True:
# Keep the script alive.
time.sleep_ms(100)
How to modify the MicroPython BLE version
You can adapt this version by changing:
- the advertised device name
- the service and characteristic UUIDs
- the command words handled in
bt_irq() - the value written back to the characteristic
How to modify this code
To turn this into your own BLE project, change:
- the device name in
BLEDevice::init(...) - the service and characteristic UUIDs
- the write callback so it reacts to your own commands
- the data stored in the characteristic
Useful next changes include:
- send sensor values by updating the characteristic periodically
- create more than one characteristic for different kinds of data
- add
NOTIFYso the board pushes updates to the phone without waiting for a read request
A common pattern is:
- one characteristic for commands from the phone
- one characteristic for sensor data from the board