当前位置: 首页 > news >正文

ESP32 008 MicroPython Web框架库 Microdot 实现的网络文件服务器

在这里插入图片描述

  • 以下是整合了所有功能的完整 main.py(在ESP32 007 MicroPython 适用于 Python 和 MicroPython 的小型 Web 框架库 Microdot基础上),实现了:
    • Wi‑Fi 自动连接(支持静态 IP);
    • SD 卡挂载;
    • /sd/www/ 读取 HTML 和静态文件
    • 浏览器浏览 /files 自动列出 SD 卡根目录文件并提供下载链接
    • 通过 /download/ 路由下载文件
    • 通过 /upload/ 路由上传文件
#from lib.microdot import Microdot, Response
from microdot import Microdot, Response , redirect
from wifi import connect_wifi
from scard import SDCard
from machine import SPI, Pin
import os#  替代  if os.path.isfile(full_path_www):
def is_file(path):try:return not (os.stat(path)[0] & 0x4000)  # 非目录except:return False
# 挂载 SD 卡
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 Trueexcept Exception as e:print("❌ SD 卡挂载失败:", e)return False# 启动网络与文件系统
connect_wifi()
mount_sd()Response.default_content_type = 'text/html'
app = Microdot()# 根目录 —— 加载 /sd/www/index.html
@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# 列出 SD 根目录所有文件,可点击下载
# @app.route('/files')
# def list_sd_files(req):
#     try:
#         files = os.listdir('/sd')
#         html = "<!DOCTYPE html><html><head><meta charset='utf-8'><title>SD 文件列表</title></head><body>"
#         html += "<h1>📁 SD 卡文件列表</h1><ul>"
#         for name in files:
#             full_path = '/sd/' + name
#             if is_file(full_path):
#                 html += f'<li><a href="/download/{name}">{name}</a></li>'
#         html += "</ul></body></html>"
#         return html
#     except Exception as e:
#         return f"<h1>无法读取 SD 卡文件</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 "未指定文件名", 400filepath = '/sd/' + filenameif not is_file(filepath):return "文件不存在", 404try: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 "请求类型错误", 400boundary = content_type.split("boundary=")[-1]body = req.body# 分割上传内容parts = 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]  # 去除结束标记# 写入到 SD 卡with open('/sd/' + filename, 'wb') as f:f.write(file_data)return redirect(f"/files?msg=文件 {filename} 上传成功!")return "未找到上传文件", 400except Exception as e:return f"上传失败: {e}", 500# 下载接口 —— 浏览器触发下载行为 —— 保持 SD 卡原文件名
@app.route('/download/<filename>')
def download_file(req, filename):filepath = '/sd/' + filenameif not is_file(filepath):return '文件不存在', 404try: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 '读取失败', 500return '404 Not Found', 404# 启动服务器
app.run(host='0.0.0.0', port=80)
http://www.lqws.cn/news/543853.html

相关文章:

  • A Machine Learning Approach for Non-blind Image Deconvolution论文阅读
  • 金蝶云星空客户端自定义控件插件-WPF实现自定义控件
  • 电磁波是如何传递信息的?
  • 鸿蒙 List 组件解析:从基础列表到高性能界面开发指南
  • 前端 E2E 测试实践:打造稳定 Web 应用的利器!
  • 海外 AI 部署:中国出海企业如何选择稳定、安全的云 GPU 基础设施?
  • 扬州搓澡非遗解码:三把刀文化的“水包皮“
  • 010 【入门】链表入门题目-合并两个有序链表
  • Linux驱动学习day9(异常与中断处理)
  • 华为云Flexus+DeepSeek征文|基于Dify构建故事绘本制作工作流
  • Spark 写入hive表解析
  • Spring Boot项目开发实战销售管理系统——系统设计!
  • 知名流体控制解决方案供应商“永盛科技”与商派ShopeX达成B2B商城项目合作
  • iOS 远程调试与离线排查实战:构建非现场问题复现机制
  • 报道称CoreWeave洽谈收购Core Scientific,后者涨超30%
  • NV025NV033美光固态闪存NV038NV040
  • 《二分枚举答案(配合数据结构)》题集
  • Python Selenium 滚动到特定元素
  • Selenium基本用法
  • Spring Boot 性能优化与最佳实践
  • 6.27_JAVA_面试(被抽到了)
  • 洛谷P5021 [NOIP 2018 提高组] 赛道修建
  • 深入理解 Linux `poll` 模型:`select` 的增强版
  • 记录一次飞书文档转md嵌入vitepress做静态站点
  • 微信小程序进度条progress支持渐变色
  • Stable Diffusion入门-ControlNet 深入理解-第三课:结构类模型大揭秘——深度、分割与法线贴图
  • 【LeetCode 热题 100】42. 接雨水——(解法三)单调栈
  • FPGA在嵌入式图像处理中的深度应用!
  • 深圳中青宝互动网络股份有限公司游戏运维工程师面试题(笔
  • python实战项目79:采集知乎话题下的所有回答