# 🎬 用ESP32-S3实施廉价KVM-over-IP — 完整折腾报告
-
项目仓库: https://github.com/peterhon168/esp32-kvm-webcontrol
源项目: KMChris/esp32-kvm-ip
日期: 2026-06-18
第一部分:技术报告
一、缘起:为什么要做这个?
远程管理服务器通常需要:
- iDRAC/iLO/iBMC — 企业级方案,贵,老主板没有(我手上另一块超微的X10-Dai也一样没有)
- PiKVM — 好方案,但树莓派被炒到天价,而且HDMI转CSI模块也不便宜
- 串口/SSH — 能敲命令,但看不到BIOS、看不到启动过程、装系统抓瞎
目标:用 ESP32-S3(¥25)+ USB HDMI采集卡(¥25) 实现一个能看画面、能键鼠操作的远程KVM(供货商为PDD)。
二、硬件清单 & 成本
组件 型号 成本 说明 主控 ESP32-S3 N16R8 ~¥25 双核240MHz,USB OTG,WiFi 采集卡 MS2103 USB HDMI (345f:2130) ~¥25 USB 3.0,支持1080p@30 MJPEG 目标机 X99双路工作站 已有 被控机器,HDMI+USB接入 宿主主机 TrueNAS SCALE / Ubuntu VM 已有 跑流服务+网桥 总硬件增量成本: ¥80(相比PiKVM动辄¥500+)
三、系统架构
┌───────────────┐ HDMI ┌──────────────────┐ │ 目标机 │──────────→│ USB 2103 采集卡 │ │ (X99 工作站) │ │ (MS2103芯片) │ │ │ USB │ │ │ │──────────→│ ESP32-S3 │ └───────────────┘ │ (HID注入) │ └────────┬─────────┘ │ WiFi UDP :4210 ▼ ┌────────────────────────────────────────────────────┐ │ 网络层 │ ├────────────────────────────────────────────────────┤ │ KVM网桥 (Python): WebSocket ←→ UDP 转换 │ │ 视频流: ffmpeg → Python MJPEG HTTP Server :8000 │ │ Web UI: http://kvm-bridge-ip:18088 │ └────────────────────────────────────────────────────┘数据流
浏览器 ←WebSocket JSON→ KVM网桥 ←UDP 16字节包→ ESP32-S3 ←USB HID→ 目标机 浏览器 ←HTTP MJPEG──→ MJPEG流服务器 ←pipe── ffmpeg ←v4l2── 采集卡 ←HDMI── 目标机延迟实测
环节 延迟 视频采集 → 串流 ~33ms (30fps) 网络传输 <1ms (局域网) 鼠标键盘注入 ~5ms (UDP→ESP32→USB) 端到端画面延迟 ~100-150ms (不含显示器) 四、ESP32-S3 固件
4.1 固件修改(vs 源项目)
源项目
KMChris/esp32-kvm-ip原本设计是一个Windows客户端通过UDP直连ESP32,需要Windows跑一个hook程序捕获键鼠。我们改成了:- WiFi修复:认证模式
WIFI_AUTH_WPA2_WPA3_PSK→WIFI_AUTH_WPA2_PSK- 原因:sdkconfig没开WPA3,连WiFi永远失败
- 重连逻辑修复:把事件回调内的
vTaskDelay改为独立reconnect_task- 原因:WiFi事件循环内阻塞导致后续事件无法处理
- UDP协议不变:16字节固定包格式,兼容原始协议
4.2 编译烧录
# 在Windows ESP-IDF环境 idf.py build flash monitor # OTG口接目标机USB,UART口接电脑(可以同时插)4.3 ESP32 IP分配
ESP32连WiFi后通过DHCP获取IP。在我们的网络里分配到 192.168.0.209。
五、USB HDMI采集卡 — MS2103 深坑记录
5.1 芯片识别
ID 345f:2130 MACROSILICON OCap Video UVC 1.00, USB 3.0 SuperSpeed 支持格式: Raw: YUYV 4:2:2 最高 3840×2160 MJPEG: Motion-JPEG 最高 3840×21605.2 血泪坑:v4l2不兼容
这个MS2103芯片是出了名的奇葩——标准v4l2 ioctl全都不吃:
VIDIOC_S_FMT→Inappropriate ioctl for deviceVIDIOC_G_FMT→ 同上VIDIOC_REQBUFS→ 同上VIDIOC_STREAMON→ 同上- 直接
read()→Invalid argument
市面上唯一能驱动它的只有 ffmpeg(内置了MS210x的workaround)。
5.3 最终方案
# 下载静态ffmpeg二进制(John Van Sickle编译版) wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz tar -xf ffmpeg-release-amd64-static.tar.xz # 捕获MJPEG帧并通过Python服务推流 ffmpeg -f v4l2 -input_format mjpeg -video_size 1920x1080 -framerate 30 \ -i /dev/video0 -f image2pipe -vcodec copy - | python3 mjpeg_server.pyPython MJPEG服务器用纯标准库(http.server + queue + threading),不需要装任何第三方包。
六、宿主机的选择折腾
6.1 尝试路线
(Hermes Agent的宿主机truenas,ubuntu是.240,没有给他授权root权限,所以下面报了一堆错)
路线 结果 原因 直接插240 Ubuntu VM
不行VM没有物理USB口 TrueNAS USB Passthrough到240
热插不生效需要重启VM,但Hermes网关在240上不能断 TrueNAS直接装uStreamer(apt)
封锁TrueNAS策略禁用包管理器 TrueNAS Docker
封锁dockerd需要root TrueNAS Python原生流服务
成功/var/tmp可写、有Python3.11+PIL、无noexec ffmpeg静态二进制跑在TrueNAS
成功下载到/var/tmp,可执行 TrueNAS Init Script开机自启
成功midclt call initshutdownscript.create 6.2 架构决策
采集卡最终插在 TrueNAS (192.168.0.160) 上,因为:
- TrueNAS 有 USB 3.0 口
- 采集卡通过 VM USB Passthrough 热添加不成功(其实是成功的,全过程没有重启宿主机及VM)nid
- TrueNAS 的
/var是 ZFS 数据集(非 tmpfs),文件重启不丢 - Python3.11 + PIL 可用
/var/tmp没挂noexec
6.3 开机自启配置(TrueNAS Init Script)
midclt call initshutdownscript.create '{ "type": "SCRIPT", "script": "/var/tmp/mjpeg_server.py", "when": "POSTINIT", "enabled": true, "timeout": 60 }'七、KVM网桥
7.1 kvm_bridge 架构
# server.py — 核心逻辑 # 1. WebSocket服务器 → 接收浏览器键鼠事件(JSON) # 2. UDP客户端 → 转发给ESP32(16字节二进制包) # 3. HTTP服务器 → 提供Web UI + 配置API # 配置 config.env ESP_IP=192.168.0.209 # ESP32 IP ESP_PORT=4210 # ESP32 UDP端口 WS_PORT=18765 # WebSocket端口 HTTP_PORT=18088 # Web UI端口 USTREAMER_URL=http://192.168.0.160:8000/stream # MJPEG视频流7.2 Web UI 特性
- 单页面HTML + CSS + JS(无框架依赖)
- WebSocket自动重连
- 鼠标 Pointer Lock API(按Alt+L切换锁定/解锁)
- BIOS兼容(相对坐标模式)
- 视频流iframe内嵌uStreamer画面
- 连通性检测(ESP32在线→绿,离线→黄色警告)
7.3 PM2进程管理
pm2 start server.py --name kvm-bridge pm2 save八、完整搭建步骤(从零开始)
Step 1: 硬件连接
目标机HDMI → 采集卡 → 宿主机USB 目标机USB → ESP32-S3 OTG口 ESP32-S3 UART口 → 电脑(烧录用,运行时不用)Step 2: 烧录ESP32固件
# Windows + ESP-IDF idf.py build flash monitor # 记下ESP32的IP(从路由器DHCP表查)Step 3: 宿主机上部署流服务
# 如果是Ubuntu(最简单) apt install ustreamer ustreamer -d /dev/video0 -m MJPEG -p 8000 -r 1920x1080 # 如果是TrueNAS(需要折腾) # 下载ffmpeg静态二进制 + Python MJPEG服务器脚本 # 部署到 /var/tmp/,配Init Script开机自启Step 4: 部署KVM网桥
# 任何有Python3的机器 pip3 install websockets cp config.env.example config.env # 编辑 config.env 填入ESP32 IP和流URL python3 server.py # 或通过PM2守护 pm2 start server.py --name kvm-bridgeStep 5: 打开浏览器
http://<bridge-ip>:18088 → 点击「连接」建立WebSocket → 点击「加载」显示视频流 → 点击视频区域 → 锁定鼠标 → 开始操作九、避坑总结
┌─────────────────────────────────────────────────────────────┐ │ 🕳️ 坑1: MS2103采集卡标准v4l2 ioctl不能用 │ │ ✅ 解决: 只能用ffmpeg(有内置workaround) │ │ │ │ 🕳️ 坑2: TrueNAS是"安全加固"系统 │ │ ✅ 解决: apt/Docker都被锁,用静态二进制+Python纯std库 │ │ │ │ 🕳️ 坑3: TrueNAS默认不许跑二进制(noexec) │ │ ✅ 解决: /var/tmp 没有noexec,放那 │ │ │ │ 🕳️ 坑4: VM USB热插不生效 │ │ ✅ 解决: 既然Hermes在VM上不能重启,物理机直插跑服务 │ │ │ │ 🕳️ 坑5: ESP32连不上WiFi │ │ ✅ 解决: 固件认证改 WPA2_PSK,重连改独立task │ │ │ │ 🕳️ 坑6: ESP32断线后不自动重连 │ │ ✅ 解决: 事件回调内vTaskDelay阻塞 → 独立reconnect_task │ └─────────────────────────────────────────────────────────────┘项目地址: https://github.com/peterhon168/esp32-kvm-webcontrol
参考: https://github.com/KMChris/esp32-kvm-ip
采集卡问题参考: https://www.mjt.me.uk/posts/fixing-missing-macrosilicon-ms2109/

-
,
T terry 固定了此主题
-
,系统 取消固定了此主题
-
牛逼,建议整个套件挂淘宝,多一个选择
-
牛逼,建议整个套件挂淘宝,多一个选择