尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

esp-iot-solution USB Hub 双摄像头示例前端 Web 显示组件的编译与二次开发指南

发布时间:2026/9/20 20:50:23

资讯中心
01
ARTICLE

esp-iot-solution USB Hub 双摄像头示例前端 Web 显示组件的编译与二次开发指南

esp-iot-solution USB Hub 双摄像头示例前端 Web 显示组件的编译与二次开发指南
esp-iot-solution USB Hub 双摄像头示例前端 Web 显示组件的编译与二次开发指南【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution本篇技术指南聚焦于 esp-iot-solution 仓库中examples/usb/host/usb_hub_dual_camera示例的前端组件frontend_source完整讲解从 Node.js 环境准备、pnpm 依赖安装、Vite 构建到 gzip 压缩产物并发布到 SPIFFS 的整条链路并结合 Vue 3 Vuetify 3 源码剖析页面与 ESP32 HTTP 后端之间的 API 交互机制。读完本文你将掌握如何在本机独立编译该 MJPEG 多摄像头预览页面、理解其相机列表 / 激活 / 截图 / 关闭的完整工作流程并具备基于现有源码进行界面定制与二次开发的能力。组件定位一个跑在 ESP32 上的摄像头管理 Web 前端examples/usb/host/usb_hub_dual_camera是一个演示如何通过 ESP-IDF 的usb_host_uvc组件连接 USB 摄像头、并通过 HTTP 服务器在浏览器中预览 MJPEG 图像的示例。它支持 USB Hub 扩展多个摄像头但同时打开的摄像头数量受 USB 主机硬件资源限制该限制由设备端动态上报前端会据此控制Add Camera按钮的可用性见下文cameraNumberLimit说明。整个示例目录结构如下frontend_source本指南的主角Vue 3 TypeScript Vuetify 3 构建的浏览器端页面源码mainESP32 固件源码包含app_https.cHTTP/HTTPS 服务、app_uvc.cUVC 相机管理、app_wifi.cSoftAP 连接等模块scripts/gzip_webserver_files.sh将前端构建产物 gzip 压缩并搬运到spiffs目录的脚本spiffs已打包好的 SPIFFS 资源index.html.gz、assets/index-*.js.gz、assets/index-*.css.gz、favicon.ico.gz。前端构建产物以 gzip 形式随固件烧录到 SPIFFS 分区ESP32 在运行时解压并提供给浏览器访问因此前端开发流程的核心就是构建 → 压缩 → 覆盖spiffs目录 → 重新编译固件。环境要求与工具链根据 frontend_source/README.md构建前端组件需要满足以下条件项目要求说明Node.jsnode 20较低的 Node 版本可能无法正常运行 Vite 5 与相关依赖包管理器推荐pnpm项目提供pnpm-lock.yaml锁定文件使用 pnpm 可保证依赖版本一致源码目录examples/usb/host/usb_hub_dual_camera/frontend_source所有前端操作均在该目录下进行进入源码目录后执行编译三部曲pnpm install # install dependencies # 部分依赖需要运行 postinstall 脚本可能需要执行 pnpm approve-builds pnpm build # build # Gzip compress the build artifacts and move them to the spiffs directory. ./../scripts/gzip_webserver_files.sh其中pnpm approve-builds是 pnpm 10 之后的安全机制pnpm 默认不再自动执行依赖包中的 postinstall 脚本需要显式批准。本项目在 package.json 中通过pnpm.onlyBuiltDependencies字段预先声明了允许执行构建脚本的依赖白名单core-js、esbuild、vue-demipnpm: { onlyBuiltDependencies: [ core-js, esbuild, vue-demi ] }如果你使用的是较新的 pnpm 版本pnpm install时若提示某些依赖需要 approve按 README 提示执行pnpm approve-builds即可。若因网络或环境原因无法使用 pnpm也可以在frontend_source目录下使用npm install代替但依赖解析结果可能与锁文件存在差异建议优先 pnpm。构建脚本与产物npm scripts 全解析package.json 中定义了完整的脚本体系scripts: { dev: vite, build: run-p type-check \build-only {}\ --, preview: vite preview, build-only: vite build, type-check: vue-tsc --build --force, lint: eslint . --fix }各脚本含义如下pnpm dev启动 Vite 开发服务器默认端口 3000见 vite.config.mts 中的server.port。开发调试时可直接在浏览器中预览页面布局pnpm build正式构建它通过npm-run-all2的run-p并行执行类型检查vue-tsc --build --force与纯构建vite build保证类型安全的同时加快构建速度pnpm preview本地预览构建产物pnpm type-check单独运行 Vue 类型检查pnpm lint运行 ESLint 并自动修复。pnpm build成功后产物输出到frontend_source/dist目录Vite 默认输出目录。此时还不能直接交给固件需要下一步的 gzip 压缩。依赖栈一览从 package.json 可以看出前端组件采用了一套标准的 Vue 3 现代工程栈运行时依赖vue^3.4、vuetify^3.6Material Design 组件库、mdi/font与mdi/jsMaterial Design Icons 字体与 JS 图标、roboto-fontface、core-js构建与工程化依赖vite^5.4、typescript~5.6、vue-tsc、eslint^9、sass/sass-embedded、pinia^2.1状态管理、vue-router^4.4Vite 插件全家桶unplugin-vue-router文件路由、unplugin-auto-import自动导入、unplugin-vue-components组件自动注册、vite-plugin-vuetifyVuetify 按需加载、vite-plugin-mock-server本地 mock 服务、vite-plugin-vue-layouts布局系统。Vite 配置要点vite.config.mts 中值得关注的配置项路径别名alias: { : fileURLToPath(new URL(./src, import.meta.url)) }源码中/store/mainstore、/utils等导入即映射到src目录unplugin-vue-router自动扫描src/pages下的.vue文件生成路由并将类型声明输出到src/typed-router.d.tsAutoImport自动导入 Vue API 并生成src/auto-imports.d.ts这就是页面代码中可以直接使用onBeforeMount、watch、ref而无需手动 import 的原因Components自动注册src/components下的组件并生成src/components.d.tsVuetify({ autoImport: true })按需引入 Vuetify 组件减小产物体积mockServer()开发模式下提供本地 mock 接口方便脱离硬件调试前端页面。把构建产物发布到 SPIFFSgzip 脚本原理构建完成后执行./../scripts/gzip_webserver_files.sh该脚本gzip_webserver_files.sh的核心逻辑如下SRC_DIR$(realpath $(dirname $0)/../frontend_source/dist) DEST_DIR$(realpath $(dirname $0)/../spiffs) rm -fr $DEST_DIR mkdir -p $DEST_DIR find $SRC_DIR -type f | while read -r FILE; do REL_PATH${FILE#$SRC_DIR/} DEST_PATH$DEST_DIR/$REL_PATH.gz mkdir -p $(dirname $DEST_PATH) gzip -c $FILE $DEST_PATH done其工作流程为定位frontend_source/dist为源目录spiffs为目标目录清空并重建spiffs目录遍历dist下所有文件保持相对目录结构不变逐个gzip -c压缩并输出为xxx.gz。执行后spiffs目录中会出现类似仓库当前已包含的产物spiffs/ ├── assets/ │ ├── index-DkJESlaa.js.gz │ └── index-DximumU_.css.gz ├── favicon.ico.gz └── index.html.gz之后重新编译固件即可将最新的页面打包进 SPIFFS 分区。相关分区划分见 partitions.csv不同芯片的编译配置见sdkconfig.defaults及sdkconfig.defaults.esp32p4、sdkconfig.defaults.esp32s2、sdkconfig.defaults.esp32s3、sdkconfig.defaults.esp32s31。前端源码结构页面是如何组织起来的前端源码frontend_source/src采用典型的 Vue 3 组合式 Pinia 状态管理结构文件职责main.ts应用入口createApp(App)后注册全部插件并挂载#appApp.vue根组件v-approuter-view /pages/index.vue主页相机卡片列表 Add Camera 选择区components/CameraCard.vue单个相机的预览卡片负责拉流、截图、关闭store/mainstore.tsPinia store集中管理相机列表与激活状态轮询后端状态plugins/index.ts 与 plugins/vuetify.ts插件注册与 Vuetify 初始化router/index.ts路由配置utils.ts类型定义Camera、Resolution、CameraActivedutils.ts中定义的三个核心类型贯穿前后端通信export interface Resolution { format: string; // 如 MJPEG width: number; height: number; index: number; // 分辨率在相机能力列表中的索引 } export interface Camera { id: string | number; resolutions: Resolution[]; } export interface CameraActived { id: string | number; resolution: Resolution; }前端与 ESP32 后端的 API 契约页面不是纯展示而是通过一系列 HTTP API 与设备端 app_https.c 交互源码中可见GET /api/cameras、GET /api/stream/*等路由的注册位置。综合前端源码可以还原出完整的 API 契约1. 轮询状态GET /api/camerasmainstore.ts 中的fetchServerSideStatus()每 2 秒轮询一次该接口pages/index.vue 中setTimeout(updateServerSideStatus, 2000)const response await fetch(/api/cameras); const resjson await response.json();返回的 JSON 包含三个关键字段limitnumber设备端允许同时激活的相机数量上限写入cameraNumberLimit。主页据此判断是否仍显示 Add Camera 卡片camerasCamera[]当前连接的全部相机及其支持的分辨率列表activatedCameraActived[]设备端当前已激活的相机。store 随后合并客户端本地激活状态与服务器端状态计算可选的相机列表clientCameraOptions已被本页面激活的相机、以及已被其他客户端在服务端激活的相机都会被过滤或降级处理避免重复抢占同一路相机。2. 激活相机POST /api/activeCameraCard.vue 在组件挂载前向后端发送激活请求const activeEndPoint new URL(/api/active, location.href) fetch(activeEndPoint, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ id: props.camId, resolution: { format: props.resolution.format ?? undefined, width: props.resolution.width, height: props.resolution.height, index: props.resolution.index, } }) })请求体携带相机 id 与目标分辨率格式、宽高、能力索引。激活成功后页面状态置为pending并开始加载视频流若失败如response.ok为 false则显示对应的错误提示。3. 拉取视频流GET /api/stream/{camId}激活成功后CameraCard.vue构造带时间戳的流地址作为img的srcconst cameraImageSrcUrl new URL(/api/stream/${props.camId}, location.href) cameraImageSrcUrl.searchParams.set(ts, String(Date.now())) cameraImageSrc.value cameraImageSrcUrl.hrefESP32 后端会持续推送 MJPEG 帧数据GET /api/stream/*路由见 app_https.c浏览器通过img直接渲染该 multipart MJPEG 流。这也是 README 中仅支持预览 MJPEG 格式图像以及不推荐 Safari 浏览器的深层原因——Safari 对 MJPEG multipart 流的支持存在限制。4. 关闭相机POST /api/deactivate点击卡片右上角的关闭按钮时quit()方法先让图片指向/api/404停止渲染再向后端发送反激活请求const deactivateEndpoint new URL(/api/deactivate, location.href) await fetch(deactivateEndpoint, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ id: props.camId }) })组件卸载onBeforeUnmount时也会调用quit()确保切页或刷新后不会在设备端留下长期占用的激活通道。页面交互细节加载状态、错误处理与帧截图CameraCard.vue通过cameraStatus状态机pending | normal | err管理相机卡片的三种呈现形态pending激活请求已发出、首帧未到达显示圆形加载动画v-progress-circularnormal首帧成功加载onImageLoad正常展示实时画面err加载失败或超时覆盖层显示错误文案。两个值得注意的健壮性设计首帧超时保护激活后若 12 秒内没有渲染出任何帧setPendingTimeout自动切换为错误态并提示 Stream started but no frame was rendered避免页面无限转圈错误文案细分图片加载失败提示 Failed to render MJPEG stream激活 HTTP 失败则透传响应状态码Failed to activate camera (${response.status})。帧截图功能README 提到的网页可保存当前帧由captureFrame()实现其原理是 Canvas 快照而非后端截图canvas.value.width camImageElement.width canvas.value.height camImageElement.height ctx.drawImage(camImageElement, 0, 0, canvas.value.width, canvas.value.height) capturedImage.value canvas.value.toDataURL(image/jpeg) const link document.createElement(a) link.href capturedImage.value link.download capture_${props.camId}_${Date.now()}.jpg link.click()流程为把当前img元素绘制到隐藏canvas上 →toDataURL(image/jpeg)导出 JPEG → 创建临时a标签触发浏览器下载文件名形如capture_0_1720000000000.jpg。本地开发调试建议结合 vite.config.mts 中的vite-plugin-mock-server与pnpm dev可以脱离硬件先行开发界面在frontend_source目录执行pnpm devVite 开发服务器默认监听http://localhost:3000借助 mock 插件模拟/api/cameras等接口数据插件配置在vite.config.mts中通过mockServer()启用验证页面布局与交互逻辑界面确认无误后执行pnpm build与./../scripts/gzip_webserver_files.sh回到固件工程重新编译烧录即可在设备上体验完整功能。如需对照中文说明仓库内还提供了 README_zh.md内容与本指南核心编译步骤一致。总结USB Hub 双摄像头示例的前端组件frontend_source是一套完整的 Vue 3 Vuetify 3 Pinia Vite 工程其开发闭环清晰可控pnpm install → pnpm build → gzip_webserver_files.sh → 固件编译烧录。页面通过GET /api/cameras轮询相机清单、POST /api/active激活指定分辨率的相机、GET /api/stream/{id}渲染 MJPEG 流、POST /api/deactivate释放资源配合 Canvas 实现前端帧截图构成了一个可在浏览器中管理多路 USB 相机的完整 Web 应用。掌握上述构建流程与 API 契约后无论是修改界面样式、增加新的控制按钮还是对接后端新增能力都可以在现有源码基础上快速展开。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。