以下是整合了所有功能的完整 main.py
(在ESP32 007 MicroPython 適用于 Python 和 MicroPython 的小型 Web 框架庫 Microdot基礎上),實現了: Wi?Fi 自動連接(支持靜態 IP); SD 卡掛載; 從 /sd/www/
讀取 HTML 和靜態文件 瀏覽器瀏覽 /files
自動列出 SD 卡根目錄文件并提供下載鏈接 通過 /download/
路由下載文件 通過 /upload/
路由上傳文件
from microdot import Microdot, Response , redirect
from wifi import connect_wifi
from scard import SDCard
from machine import SPI, Pin
import os
def is_file ( path) : try : return not ( os. stat( path) [ 0 ] & 0x4000 ) except : return False
def mount_sd ( ) : try : spi = SPI( 2 , baudrate= 1_000_000, sck= Pin( 5 ) , mosi= Pin( 6 ) , miso= Pin( 7 ) ) cs = Pin( 4 , Pin. OUT) sd = SDCard( spi, cs) os. mount( sd, '/sd' ) print ( "? SD 卡掛載成功:" , os. listdir( '/sd' ) ) return True except Exception as e: print ( "? SD 卡掛載失敗:" , e) return False
connect_wifi( )
mount_sd( ) Response. default_content_type = 'text/html'
app = Microdot( )
@app. route ( '/' )
def index ( req) : try : with open ( '/sd/www/index.html' ) as f: return f. read( ) except Exception as e: return f"<h1>無法加載主頁</h1><p> { e} </p>" , 500
@app. route ( '/files' )
def list_sd_files ( req) : msg = req. args. get( 'msg' , '' ) try : files = os. listdir( '/sd' ) html = """<!DOCTYPE html><html><head><meta charset='utf-8'><title>SD 文件列表</title></head><body>""" html += "<h1>📁 SD 卡文件列表</h1>" if msg: html += f"<p style='color: green;'> { msg} </p>" html += """<form method="POST" action="/upload" enctype="multipart/form-data"><input type="file" name="file"><button type="submit">上傳文件</button></form><hr><ul>""" for name in files: full_path = '/sd/' + nameif is_file( full_path) : html += f"""<li><a href="/download/ { name} "> { name} </a><form style="display:inline" method="POST" action="/delete"><input type="hidden" name="filename" value=" { name} "><button type="submit" onclick="return confirm('確定刪除文件 { name} 嗎?');">刪除</button></form></li>""" html += "</ul></body></html>" return htmlexcept Exception as e: return f"<h1>無法讀取 SD 卡文件</h1><p> { e} </p>" , 500
@app. route ( '/delete' , methods= [ 'POST' ] )
def delete_file ( req) : filename = req. form. get( 'filename' ) if not filename: return "未指定文件名" , 400 filepath = '/sd/' + filenameif not is_file( filepath) : return "文件不存在" , 404 try : os. remove( filepath) return redirect( f"/files?msg=文件 { filename} 刪除成功!" ) except Exception as e: return f"刪除文件失敗: { e} " , 500
@app. route ( '/upload' , methods= [ 'POST' ] )
def upload_file ( req) : try : content_type = req. headers. get( 'Content-Type' , '' ) if 'multipart/form-data' not in content_type: return "請求類型錯誤" , 400 boundary = content_type. split( "boundary=" ) [ - 1 ] body = req. bodyparts = body. split( b'--' + boundary. encode( ) ) for part in parts: if b'Content-Disposition' in part and b'filename=' in part: header, file_data = part. split( b'\r\n\r\n' , 1 ) header_str = header. decode( ) filename = header_str. split( 'filename="' ) [ - 1 ] . split( '"' ) [ 0 ] file_data = file_data. rsplit( b'\r\n' , 1 ) [ 0 ] with open ( '/sd/' + filename, 'wb' ) as f: f. write( file_data) return redirect( f"/files?msg=文件 { filename} 上傳成功!" ) return "未找到上傳文件" , 400 except Exception as e: return f"上傳失敗: { e} " , 500
@app. route ( '/download/<filename>' )
def download_file ( req, filename) : filepath = '/sd/' + filenameif not is_file( filepath) : return '文件不存在' , 404 try : f = open ( filepath, 'rb' ) return Response( f, headers= { 'Content-Type' : 'application/octet-stream' , 'Content-Disposition' : 'attachment; filename="{}"' . format ( filename) } ) except Exception as e: return f"讀取文件失敗: { e} " , 500 @app. route ( '/<path:path>' )
def serve_static ( req, path) : full_path_www = '/sd/www/' + pathif is_file( full_path_www) : try : return open ( full_path_www) . read( ) except : return '讀取失敗' , 500 return '404 Not Found' , 404
app. run( host= '0.0.0.0' , port= 80 )