import socket
import json
import os
import uos
import time
import _thread
import machine
import sdcard
# =====================================================
# CONFIG
# =====================================================
WIDTH = 32
HEIGHT = 48
PORT = 80
SD_MOUNT = "/sd"
IMAGE_DIR = "/sd/images"
IMAGE_FILE = IMAGE_DIR + "/last.json"
MAX_FRAMES = 60
SD_FULL_THRESHOLD = 0.90 # 90%
# =====================================================
# SD CARD MOUNT
# =====================================================
def mount_sd():
try:
# Initialize SD card
sd = sdcard.SDCard(machine.SPI(1), machine.Pin(27))
print("ttttt")
# Mount filesystem
vfs = uos.VfsFat(sd)
uos.mount(vfs, SD_MOUNT)
print("SD card mounted")
if "images" not in os.listdir(SD_MOUNT):
os.mkdir(IMAGE_DIR)
except Exception as e:
print("SD mount failed:", e)
mount_sd()
# =====================================================
# SD CARD CLEANUP
# =====================================================
def cleanup_sd_if_needed():
stat = os.statvfs(SD_MOUNT)
total = stat[0] * stat[2]
free = stat[0] * stat[3]
used_ratio = 1 - (free / total)
if used_ratio < SD_FULL_THRESHOLD:
return
print("SD card nearly full, cleaning up...")
files = []
for f in os.listdir(IMAGE_DIR):
path = IMAGE_DIR + "/" + f
try:
mtime = os.stat(path)[8]
files.append((mtime, path))
except:
pass
# Oldest first
files.sort()
for _, path in files:
if path == IMAGE_FILE:
continue
try:
os.remove(path)
print("Deleted:", path)
except:
pass
# Re-check usage
stat = os.statvfs(SD_MOUNT)
free = stat[0] * stat[3]
used_ratio = 1 - (free / total)
if used_ratio < SD_FULL_THRESHOLD:
break
# =====================================================
# LED PANEL DRIVER (STUB)
# =====================================================
def display_frame(frame, brightness):
"""
frame[y][x] = [r, g, b]
brightness = 0.0 – 1.0
Replace with your actual LED panel driver
"""
scale = brightness
for y in range(HEIGHT):
for x in range(WIDTH):
r, g, b = frame[y][x]
r = int(r * scale)
g = int(g * scale)
b = int(b * scale)
# panel.set_pixel(x, y, r, g, b)
# panel.show()
pass
# =====================================================
# IMAGE STORAGE
# =====================================================
def save_image(data):
cleanup_sd_if_needed()
with open(IMAGE_FILE, "w") as f:
json.dump(data, f)
def load_image():
try:
with open(IMAGE_FILE, "r") as f:
return json.load(f)
except:
return None
# =====================================================
# DISPLAY LOOP
# =====================================================
def display_loop():
while True:
data = load_image()
if not data:
time.sleep(0.5)
continue
brightness = data.get("brightness", 1.0)
if data["type"] == "static":
display_frame(data["frames"][0], brightness)
time.sleep(0.05)
elif data["type"] == "animation":
for frame in data["frames"]:
display_frame(frame, brightness)
time.sleep(data["delay"] / 1000)
# =====================================================
# HTTP SERVER
# =====================================================
def start_server():
addr = socket.getaddrinfo("0.0.0.0", PORT)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)
print("Web server running on port", PORT)
while True:
conn, addr = s.accept()
req = conn.recv(8192).decode()
if "POST /upload" in req:
body = req.split("\r\n\r\n", 1)[1]
data = json.loads(body)
if data["type"] == "animation":
if len(data["frames"]) > MAX_FRAMES:
conn.send("HTTP/1.1 413 Payload Too Large\r\n\r\n")
conn.close()
continue
save_image(data)
conn.send("HTTP/1.1 200 OK\r\n\r\nOK")
else:
conn.send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n\r\n"
)
conn.send(HTML_PAGE)
conn.close()
# =====================================================
# HTML + JAVASCRIPT
# =====================================================
HTML_PAGE = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>LED Panel Upload</title>
<style>
canvas {{
border: 1px solid #000;
image-rendering: pixelated;
width: 160px;
height: 240px;
}}
</style>
</head>
<body>
<h2>LED Panel Upload</h2>
<input type="file" id="file">
<br><br>
<label>Brightness:</label>
<input type="range" id="brightness" min="1" max="100" value="100">
<span id="bval">100%</span>
<br><br>
<button onclick="upload()">Upload</button>
<p id="error" style="color:red;"></p>
<h3>Preview (32×48)</h3>
<canvas id="preview" width="{WIDTH}" height="{HEIGHT}"></canvas>
<script>
const W = {WIDTH};
const H = {HEIGHT};
const MAX_FILE_SIZE = 1000000;
const MAX_GIF_FRAMES = {MAX_FRAMES};
const slider = document.getElementById("brightness");
const bval = document.getElementById("bval");
slider.oninput = () => bval.textContent = slider.value + "%";
function showError(msg) {{
document.getElementById("error").textContent = msg;
}}
function clearError() {{
document.getElementById("error").textContent = "";
}}
function upload() {{
clearError();
const file = document.getElementById("file").files[0];
if (!file) return;
if (file.size > MAX_FILE_SIZE) {{
showError("File too large (max 1 MB)");
return;
}}
if (file.type === "image/gif") {{
handleGIF(file);
}} else {{
handleStatic(file);
}}
}}
function extractFrame(ctx) {{
const pixels = ctx.getImageData(0, 0, W, H).data;
const frame = [];
for (let y = 0; y < H; y++) {{
const row = [];
for (let x = 0; x < W; x++) {{
const i = (y * W + x) * 4;
row.push([pixels[i], pixels[i+1], pixels[i+2]]);
}}
frame.push(row);
}}
return frame;
}}
function handleStatic(file) {{
const img = new Image();
img.onload = () => {{
const canvas = document.getElementById("preview");
const
"""