I have several cameras at home — a few Xiaomi cameras, some DIY ESP32 cameras, and multiple Raspberry Pi CSI cameras. I'd been using cloud storage solutions, but I was never comfortable with them: vendor lock-in, network dependency, and the costs add up. So I decided to build my own NVR system, called MiBeeNvr.
Why Build MiBeeNvr
To be honest, I was never satisfied with existing cloud storage solutions. Take Xiaomi cameras, for example. By default, you can only view them through the Mi Home app. Recordings are either stored on an SD card (limited capacity, frequent plugging/unplugging) or in the cloud. Cloud storage costs tens of dollars per month, and there's the privacy concern — you never know when the manufacturer might use your video data for AI training or sell it to third parties. Not to mention vendor lock-in — switching platforms is nearly impossible.
ESP32 cameras have a similar problem. I built several ESP32 cameras, storing recordings on SD cards, but viewing and playback were inconvenient. I needed a unified management platform.
I also tried other open-source solutions: ZoneMinder requires a LAMP stack — installing and deploying it is more complex than my entire project; Shinobi's configuration is a nightmare; and some smaller projects are basically unmaintained. Frigate is nice but primarily focused on AI detection and depends on Docker — too heavy.
In short, I wanted something that is:
- A single binary file — download and run
- Lightweight enough to run on a Raspberry Pi
- Supports multiple camera types, especially Xiaomi's proprietary protocol
- Clean Web interface without frontend complexity
- Auto-cleanup of old recordings, won't fill up the disk
After searching around, none of the existing solutions fit. So I wrote my own.
What is MiBeeNvr
MiBeeNvr is a lightweight NVR system written in Go, designed to solve local storage for home cameras.
Overall Architecture
The system has three layers:
Camera End — multiple camera types connect through different protocols:
- Xiaomi cameras use the proprietary "miss" protocol (encrypted, multi-layer)
- ESP32 cameras send HTTP JPEG/MJPEG streams directly
- Raspberry Pi CSI cameras output standard RTSP H.264 via MediaMTX
Protocol Bridge Layer — proprietary protocols get converted to standard RTSP:
-
go2rtchandles Xiaomi's miss protocol decryption and transcodes it to RTSP -
MediaMTXconverts Raspberry Pi CSI interface video to RTSP
MiBeeNvr Core — handles the recording logic:
- REST API receives all video streams
- Recording engine segments incoming video into MP4 files
- SQLite stores metadata (pure Go, no CGO, no separate DB installation)
- Auto-cleanup daemon removes old recordings per retention policy
- HLS live streaming for real-time viewing (up to 4 concurrent)
Access Methods — users interact through:
- Built-in Web UI (Svelte 5 SPA, embedded in the single binary)
- WebDAV (read-write) and FTP for file-level access
- Prometheus metrics for system monitoring
Result: cameras → protocol bridge → MiBeeNvr core → user access, all in a single binary.
Recording Pipeline
Video processing follows a straightforward pipeline:
-
Input — RTSP streams are handled via
gortsplib, HTTP JPEG streams are periodically grabbed as frames -
Decode & Mux — RTP packets are depacketized via
pion/rtp, then muxed into MP4 viago-mp4 - Storage — video is segmented into files (configurable: 30s or 10m intervals), SQLite tracks metadata, files go to disk
The frontend uses Svelte 5 — the entire SPA is compiled to static assets and embedded into the Go binary. Deployment is a single file, no separate Web server needed.
Backend tech stack:
- Go 1.26 + modernc.org/sqlite (pure Go, no CGO dependency)
- chi routing library, clean and efficient
- gortsplib for RTSP/RTP protocol
- pion/rtp for real-time streaming
SQLite was chosen because it's single-file, pure Go, performs well enough for home use, supports concurrent access, and most importantly, doesn't require a separate database installation.
Design Philosophy
The entire project's design philosophy is "simple and straightforward:"
- Single binary file, no external dependencies
- Supports cross-compilation, runs on AMD64/ARM64
- YAML configuration, intuitive
- Built-in Web interface, open browser to use
- Minimal resource usage, runs smoothly on Raspberry Pi 4
Key Features
- Supports multiple camera protocols: RTSP (H.264/H.265), HTTP JPEG
- Built-in Web interface with dark/light theme switching
- Chinese/English bilingual support
- WebDAV (read-write), FTP, REST API
- MQTT-triggered recording, ideal for smart home integration
- Prometheus monitoring metrics
- Per-camera independent retention policies
- MP4 segmented recording, auto-cleanup of old files
- Supports HLS live streaming (up to 4 concurrent)
My Actual Deployment
I run it on an ARM64 mini host with 512MB RAM and 2GB storage. The system runs very stably — basically set it and forget it.
Connected 4 cameras, each with its own characteristics:
-
Raspberry Pi CSI Camera — RTSP bridge via MediaMTX, converting CSI interface video to standard RTSP. Configured as
rtsp_h264. -
ESP32-S3 Camera — DIY, running MJPEG stream via HTTP protocol. Configured as
http_jpeg. -
Xiaomi Camera (Balcony) — Protocol conversion via go2rtc (Xiaomi proprietary → RTSP), 2K resolution, configured as
rtsp_h265. - Xiaomi Camera (Living Room) — Same as above, 1080P.
Configuration is 30-second segment recording with 1-day retention. This interval is a trade-off: too short creates too many files, too long makes it inconvenient to look up incidents. WebDAV (read-write) and FTP are enabled for convenient phone viewing and backup.
The Web interface is clean — camera management and recording lists are straightforward.
Settings page:
Configuration File
The complete configuration file looks like this — YAML format, clear at a glance:
server:
listen: ":9090"
storage:
root_dir: "/mnt/data/nvr"
segment_duration: "30s"
cameras:
- id: "rpi-csi-cam"
name: "RPi CSI Camera"
protocol: "rtsp_h264"
url: "rtsp://10.0.1.100:8554/stream"
enabled: true
- id: "esp32-cam"
name: "ESP32-S3 Camera"
protocol: "http_jpeg"
url: "http://10.0.1.101/capture"
enabled: true
- id: "xiaomi-balcony"
name: "Xiaomi Camera"
protocol: "rtsp_h265"
url: "rtsp://10.0.1.102:8554/xiaomi_stream"
enabled: true
cleanup:
retention_days: 30
disk_threshold_percent: 95
auth:
username: "admin"
password_hash: "Use mibee-nvr hash-password command to generate"
webdav:
enabled: true
path_prefix: "/dav"
read_write: true
ftp:
enabled: true
port: 2121
Xiaomi Camera Integration
Xiaomi camera protocol is a major headache. It uses its proprietary "miss" (Mi Secure Streaming) protocol with multi-layer encryption, without a standard RTSP interface. Even if you know the camera's IP, you can't pull a stream with VLC.
Fortunately, there's go2rtc, a lifesaver. The integration works in four steps:
- Account auth & key exchange — go2rtc logs into your Xiaomi account, retrieves device lists and encryption keys from Xiaomi Cloud
- Establish P2P connection — go2rtc initiates a miss protocol handshake with the camera, establishing a peer-to-peer link
- Video stream relay — the camera sends its encrypted miss stream to go2rtc continuously; go2rtc decrypts and transcodes it into a standard RTSP stream (H.265); MiBeeNvr receives this as a normal RTSP camera and records segmented MP4 files
- Independent access — once set up, you no longer need the Mi Home app or Xiaomi cloud subscription; all recordings are accessible via MiBeeNvr's Web UI, WebDAV, or FTP
The entire process requires no firmware flashing, no camera disassembly, and no Xiaomi cloud storage subscription. go2rtc handles all the protocol conversion.
go2rtc Deployment
The easiest way to deploy go2rtc is with Docker:
# Create configuration file
cat > go2rtc.yaml << EOF
streams:
xiaomi_balcony:
- xiaomi://your_account:cn@10.0.1.100?did=your_camera_did&model=isa.camera.hlc7
xiaomi_living_room:
- xiaomi://your_account:cn@10.0.1.101?did=your_camera_did&model=isa.camera.mj200
rtsp:
listen: ":8554"
EOF
# Run container
docker run -d --name go2rtc \
-p 8554:8554 \
-p 1984:1984 \
-v $(pwd)/go2rtc.yaml:/config.yaml \
alexxit/go2rtc
Key points:
- The
xiaomi://protocol requires Xiaomi account and password authentication -
didis the device's unique identifier,modelis the device model (can be found in the Mi Home app) - go2rtc automatically handles P2P connection and miss protocol decryption
- The final standard RTSP stream is exposed on port 8554, and MiBeeNvr connects to it like any normal camera
Then point to go2rtc in MiBeeNvr's config:
cameras:
- id: "xiaomi-balcony"
name: "Xiaomi Camera"
protocol: "rtsp_h265"
url: "rtsp://localhost:8554/xiaomi_balcony"
enabled: true
Pitfalls Encountered
Xiaomi camera integration has several pitfalls:
- First-time network connection: Xiaomi cameras must be able to reach the internet for key exchange with Xiaomi servers. After connection is established, subsequent transmission is over LAN.
-
Device ID acquisition: Each camera's
didis unique. Use go2rtc's WebUI (port 1984) for auto-discovery, or dig through the Mi Home app. - Not all models are supported: go2rtc maintains a compatibility list — check before buying a camera.
- H.265 vs H.264: Newer Xiaomi cameras mostly use H.265. MiBeeNvr supports both codecs, but H.265 saves storage space.
ESP32 Camera Projects
While working on MiBeeNvr, I also built several ESP32 camera firmware projects. ESP32 cameras had their share of pitfalls, but were also quite interesting.
I built three firmware projects with different positioning, all designed as upstream capture endpoints for MiBeeNvr — cameras handle video capture, MiBeeNvr handles unified storage and management.
MiBeeCam — ESP32-S3-A10 Solution
GitHub · MIT License
This is the most successful solution. ESP32-S3-A10 dev board + OV2640 camera (8225N module), 16MB Flash, ESP-IDF v5.4.3 development. Features include MJPEG stream, frame-differencing motion detection, Web config interface, Prometheus metrics. Having an LCD screen makes debugging much easier.
AI Thinker ESP32-CAM — Classic Solution
GitHub · MIT License
Entry-level choice, AI Thinker ESP32-CAM dev boards are widely available for around $10-15. 4MB Flash + 4MB PSRAM, runs MJPEG stream without issues. Highlights include SD card storage and NAS upload (WebDAV/HTTP), plus adaptive dark scene detection — automatically switches to infrared mode at night. Downside: no screen, 4MB Flash is limited.
MiBeeHomeCam — XIAO ESP32-S3 Sense
GitHub · GPL v3.0
The most advanced solution. XIAO ESP32-S3 Sense board is compact and refined, with dual camera support (OV2640/OV3660), 8MB Octal PSRAM. Highlights include AVI segmented recording (real video recording, not just snapshots), FTP/WebDAV dual-protocol upload, watchdog anti-freeze, chip temperature monitoring, batch file management. Suitable for long-term stable operation.
Selection Guide
- Beginners: Choose AI Thinker ESP32-CAM — cheap with plenty of resources
- Daily use: Choose MiBeeCam — LCD screen makes debugging convenient
- Maximum features: Choose XIAO ESP32-S3 Sense — most powerful
System Service Configuration
For stable operation, I use systemd to manage MiBeeNvr:
[Unit]
Description=MiBee NVR
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=nvr
ExecStart=/mnt/data/nvr/bin/mibee-nvr -config /mnt/data/nvr/mibee-nvr.yaml
WorkingDirectory=/mnt/data/nvr
Restart=on-failure
RestartSec=5
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/mnt/data/nvr
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Save to /etc/systemd/system/mibee-nvr.service, then systemctl enable --now mibee-nvr. Auto-start on boot, auto-restart on failure.
Open Source
MiBeeNvr is open source, stars and contributions welcome:
- MiBeeNvr: https://github.com/Mi-Bee-Studio/MiBeeNvr (MIT License)
- MiBeeCam: https://github.com/Mi-Bee-Studio/luatos-esp32s3-a10-camera (MIT)
- AI Thinker ESP32-CAM: https://github.com/Mi-Bee-Studio/ai-thinker-esp32-cam (MIT)
- MiBeeHomeCam: https://github.com/Mi-Bee-Studio/seeed-esp32s3-cam (GPL v3.0)
Documentation is comprehensive, with detailed deployment and configuration instructions.
Closing Thoughts
To be honest, I built this project mainly because I was dissatisfied with all the existing solutions. Cloud storage is too expensive, open-source solutions are too heavy, and commercial products are too closed. Building my own was just right: lightweight, free, and fully under my control.
Oh, about the name MiBeeNvr — "Mi" stands for me (Mickey), "Bee" stands for... let's keep that a secret, and "Nvr" is naturally Network Video Recorder. Simple, memorable, and a bit meaningful.
If you also have home camera needs or ideas about NVR systems, feel free to reach out. Issues are welcome on GitHub.
Originally published on my blog.



Top comments (0)