Skip to content

Commit 49bdecb

Browse files
committed
Replace low-level sockets with adafruit_httpserver
Note: as of Oct 26, 2025, this code depends on a PR that hasn't yet been merged: adafruit/Adafruit_CircuitPython_ESP32SPI#218 I had to pull this branch into the ESP32SPI submodule of the circuitpython source and then build custom firmware to get this to work.
1 parent 44c1c4a commit 49bdecb

File tree

1 file changed

+39
-125
lines changed

1 file changed

+39
-125
lines changed

matrixportal/code.py

Lines changed: 39 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from adafruit_esp32spi import adafruit_esp32spi
1414
from adafruit_matrixportal.matrix import Matrix
1515
from digitalio import DigitalInOut
16+
from adafruit_httpserver import Server, Request, Response
17+
import adafruit_esp32spi.adafruit_esp32spi_socketpool as socketpool
1618

1719
# Get wifi details and more from a secrets.py file
1820
try:
@@ -43,8 +45,8 @@
4345
continue
4446
print("Connected to", str(radio.ap_info.ssid, "utf-8"), "\tRSSI:", radio.ap_info.rssi)
4547

46-
# Initialize a requests session
47-
pool = adafruit_connection_manager.get_radio_socketpool(radio)
48+
# Initialize socket pool and requests session
49+
pool = socketpool.SocketPool(radio)
4850
ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio)
4951
requests = adafruit_requests.Session(pool, ssl_context)
5052

@@ -115,137 +117,49 @@ def load_and_display_bitmap(bitmap_url):
115117
print("Loading default bitmap...")
116118
load_and_display_bitmap(DEFAULT_BITMAP_URL)
117119

118-
# Print IP address for testing
119-
print("=" * 50)
120-
print(f"MatrixPortal HTTP Server Ready!")
121-
print(f"IP Address: {radio.pretty_ip(radio.ip_address)}")
122-
print(f"Test with: curl -X POST http://{radio.pretty_ip(radio.ip_address)} -d \"<bitmap-url>\"")
123-
print("=" * 50)
120+
# Create HTTP server
121+
http_server = Server(pool, debug=True)
122+
123+
@http_server.route("/display", methods=["POST"])
124+
def display_bitmap_handler(request: Request):
125+
"""Handle POST requests with bitmap URL in body"""
126+
print(f"\nReceived POST request to /display")
127+
128+
try:
129+
# Extract URL from raw POST body
130+
body_bytes = request.body
131+
bitmap_url = body_bytes.decode('utf-8', errors='ignore').strip()
124132

125-
# Create HTTP server using ESP32 SPI API
126-
print("Creating server socket...")
127-
socket_num = radio.get_socket()
128-
print(f"Allocated socket number: {socket_num}")
133+
if not bitmap_url:
134+
print("Empty URL in POST body")
135+
return Response(request, "Empty URL in POST body", status=400)
129136

130-
# Start server on port 80
131-
radio.start_server(port=80, socket_num=socket_num)
132-
print(f"Server started on port 80, socket {socket_num}")
137+
print(f"Received URL: {bitmap_url}")
133138

134-
# Verify server is listening
135-
state = radio.server_state(socket_num)
136-
print(f"Server state: {state}")
137-
print("Listening for HTTP requests on port 80...")
139+
# Load and display the bitmap
140+
load_and_display_bitmap(bitmap_url)
138141

139-
# Socket state constants
140-
SOCKET_CLOSED = 0
141-
SOCKET_LISTEN = 1
142-
SOCKET_ESTABLISHED = 4
142+
print("Bitmap loaded and displayed successfully")
143+
return Response(request, "Bitmap displayed successfully", status=200)
143144

144-
# Buffer for accumulating request data
145-
request_buffer = b""
145+
except Exception as e:
146+
print(f"Error loading bitmap: {e}")
147+
return Response(request, f"Error loading bitmap: {e}", status=500)
148+
149+
# Start HTTP server
150+
ip = radio.pretty_ip(radio.ip_address)
151+
print("\n" + "=" * 50)
152+
print(f"Starting HTTP server at {ip}:80")
153+
http_server.start(str(ip), port=80)
154+
print(f"MatrixPortal HTTP Server Ready!")
155+
print(f"Test with: curl -X POST http://{ip}/display -d \"<bitmap-url>\"")
156+
print("=" * 50)
146157

147-
# Main server loop - poll for incoming connections
148-
print("Waiting for client connections...")
158+
# Main server loop
159+
print("Listening for HTTP requests...")
149160
while True:
150161
try:
151-
# Check the server socket state
152-
state = radio.server_state(socket_num)
153-
154-
# Only process data if a client is connected (SOCKET_ESTABLISHED)
155-
if state != SOCKET_ESTABLISHED:
156-
# No client connected, continue polling
157-
time.sleep(0.1)
158-
continue
159-
160-
# Client is connected! Check if data is available
161-
available = radio.socket_available(socket_num)
162-
163-
if available <= 0:
164-
# No data available yet, continue polling
165-
time.sleep(0.05)
166-
continue
167-
168-
# Data available - read it
169-
print(f"\nClient connected, reading {available} bytes...")
170-
chunk = radio.socket_read(socket_num, available)
171-
request_buffer += chunk
172-
173-
# Check if we have complete headers
174-
if b"\r\n\r\n" not in request_buffer:
175-
# Wait for more data if headers are incomplete
176-
continue
177-
178-
# We have a complete HTTP request (headers + body)
179-
print(f"Received request ({len(request_buffer)} bytes)")
180-
181-
try:
182-
# Parse HTTP request
183-
request_str = request_buffer.decode('utf-8', errors='ignore')
184-
lines = request_str.split('\r\n')
185-
186-
# Check if it's a POST request
187-
if lines[0].startswith('POST'):
188-
# Find Content-Length header
189-
content_length = 0
190-
for line in lines:
191-
if line.lower().startswith('content-length:'):
192-
content_length = int(line.split(':')[1].strip())
193-
break
194-
195-
# Extract body
196-
header_end = request_buffer.find(b"\r\n\r\n")
197-
body = request_buffer[header_end + 4:]
198-
199-
# Check if we have the complete body
200-
if len(body) >= content_length:
201-
# Extract URL from body
202-
bitmap_url = body[:content_length].decode('utf-8', errors='ignore').strip()
203-
204-
if bitmap_url:
205-
print(f"Received URL: {bitmap_url}")
206-
207-
try:
208-
# Load and display the bitmap
209-
load_and_display_bitmap(bitmap_url)
210-
211-
# Send success response
212-
response = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nBitmap displayed successfully\r\n"
213-
radio.socket_write(socket_num, response)
214-
print("Bitmap loaded and displayed successfully")
215-
216-
except Exception as e:
217-
print(f"Error loading bitmap: {e}")
218-
response = f"HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nError loading bitmap: {e}\r\n".encode()
219-
radio.socket_write(socket_num, response)
220-
else:
221-
print("Empty URL in POST body")
222-
response = b"HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nEmpty URL in POST body\r\n"
223-
radio.socket_write(socket_num, response)
224-
225-
# Clear buffer for next request
226-
request_buffer = b""
227-
else:
228-
# Not a POST request
229-
print(f"Not a POST request: {lines[0]}")
230-
response = b"HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nOnly POST requests are supported\r\n"
231-
radio.socket_write(socket_num, response)
232-
request_buffer = b""
233-
234-
except Exception as e:
235-
print(f"Error parsing request: {e}")
236-
response = b"HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nError processing request\r\n"
237-
try:
238-
radio.socket_write(socket_num, response)
239-
except:
240-
pass
241-
request_buffer = b""
242-
243-
gc.collect()
244-
245-
# Small delay to prevent busy-waiting
246-
time.sleep(0.1)
247-
162+
http_server.poll()
248163
except Exception as e:
249164
print(f"Server error: {e}")
250-
request_buffer = b""
251165
time.sleep(1)

0 commit comments

Comments
 (0)