Files
esp32s3-ld2453/orangepi-server/README.md
T
2026-09-11 22:09:39 +07:00

598 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ESP32-S3 LD2453 Radar — Web Visualizer cho Orange Pi
> **English version** below / Xem bản tiếng Anh ở cuối file.
Hướng dẫn cài đặt và sử dụng **Web Visualizer** trên Orange Pi (hoặc máy tính bất kỳ có Python 3.8+) để hiển thị dữ liệu radar realtime dưới dạng giao diện web đẹp trên trình duyệt.
> ✅ **Không cần giao diện desktop (Tkinter/Pygame) nữa.** Visualizer chạy nền dạng Web Server (FastAPI), bạn chỉ cần mở trình duyệt (trên điện thoại, laptop, PC…) là xem được đồ họa.
---
## 1. Chuẩn bị
### 1.1 Yêu cầu hệ thống
- Orange Pi / Raspberry Pi / PC bất kỳ có **Python 3.8+**
- Cùng mạng LAN với ESP32 (WiFi hoặc cổng LAN)
- Biết IP của ESP32 (xem Serial Monitor khi boot, hoặc vào router xem)
### 1.2 Cấu trúc thư mục
```
orangepi-server/
├── visualizer.py # Web Server (FastAPI): kết nối ESP32 + broadcast tới trình duyệt
├── requirements.txt # Python dependencies
├── templates/
│ └── index.html # Giao diện Web (HTML)
└── static/
├── style.css # Giao diện (CSS): sáng/tối, responsive, song ngữ
└── app.js # Canvas render + WebSocket client trong trình duyệt
```
### 1.3 Copy file lên Orange Pi
Copy cả thư mục `orangepi-server/` lên Orange Pi (clone repo, SCP, USB…).
Ví dụ SCP (chạy từ máy bạn):
```bash
scp -r orangepi-server/ orangepi@<ORANGE_PI_IP>:/home/orangepi/
```
---
## 2. Cài đặt Python dependencies
```bash
cd ~/orangepi-server
# (Tùy chọn nhưng khuyến nghị) Dùng virtual env
python3 -m venv venv
source venv/bin/activate
# Cài dependencies
pip install -r requirements.txt
```
**Cài global (nhanh hơn, không khuyến nghị):**
```bash
pip3 install -r requirements.txt
```
> Không cần `python3-tk` nữa — toàn bộ chạy trên web.
---
## 3. Sử dụng
### 3.1 Bật ESP32 ở chế độ Full Stage
Trong `config.h` trên Arduino IDE:
```cpp
#define DIAGNOSTIC_MODE 0 // Full mode: WiFi + WebSocket + REST API
```
Nạp code, mở Serial Monitor lấy IP ESP32. Ví dụ: `192.168.1.100`
### 3.2 Khởi động Web Visualizer trên Orange Pi
```bash
# Nếu dùng venv:
source venv/bin/activate
# Chạy (thay bằng IP ESP32 của bạn):
python3 visualizer.py 192.168.1.100
```
Các tùy chọn lệnh:
```bash
# Tùy chỉnh port (mặc định 8000):
python3 visualizer.py 192.168.1.100 --port 8080
# Giới hạn truy cập chỉ local (không cho từ ngoài):
python3 visualizer.py 192.168.1.100 --host 127.0.0.1
# Log chi tiết:
python3 visualizer.py 192.168.1.100 --verbose
```
Màn hình console sẽ hiển thị:
```
==============================================================
ESP32-S3 LD2453 Radar — Web Visualizer
==============================================================
ESP32 : ws://192.168.1.100/ws
Web UI : http://0.0.0.0:8000/
WS UI : ws://<OrangePi_IP>:8000/ws
==============================================================
```
### 3.3 Mở trình duyệt xem kết quả
Trên **bất kỳ thiết bị nào cùng LAN** (điện thoại, laptop, PC…):
```
http://<IP_OrangePi>:8000/
```
Ví dụ: Orange Pi có IP `192.168.1.50` → mở `http://192.168.1.50:8000/`
---
## 4. Tính năng giao diện Web
### 4.1 Tổng quan
| Tính năng | Mô tả |
|---|---|
| 🎨 **Giao diện đẹp** | Chủ đề sáng/tối (Light/Dark), màu sắc theo chuẩn Catppuccin |
| 🌐 **Song ngữ** | Bật/tắt Tiếng Việt / English ngay trên header |
| 📱 **Responsive** | Xem tốt trên cả mobile, tablet, desktop |
| 🧭 **Radar Canvas** | Hiển thị radar, grid 1m, vòng tròn khoảng cách, hiệu ứng quét (sweep) |
| 🎯 **Targets** | Lên đến 3 target với màu khác nhau, đường dẫn (trail), mũi tên tốc độ, nhãn tọa độ |
| 🟩 **Zones** | 3 vùng hình chữ nhật với màu & trạng thái (Có người / Trống) |
| 📊 **Thống kê** | Frames nhận, target hợp lệ, vùng có người, cập nhật lần cuối |
| 📝 **Chi tiết** | Thông tin chi tiết từng target & zone ở panel bên phải |
| ✏️ **Sửa zones** | Chỉnh tọa độ zones ngay trên web, tự động gửi cập nhật tới ESP32 |
| 🔗 **Multi client** | Nhiều trình duyệt xem cùng lúc, dữ liệu đồng bộ realtime |
### 4.2 Header
- 🟢 / 🟡 / 🔴 Trạng thái kết nối **ESP32** và **WebSocket**
- ☀️ / 🌙 Nút chuyển Sáng / Tối
- `VI / EN` Nút chuyển ngôn ngữ
### 4.3 Radar View (Canvas)
- **Grid**: Mỗi ô = 1 mét, có đánh số (−5m ← 0 → +5m ngang, 0 → 7m thẳng)
- **Vòng tròn khoảng cách**: Các vòng tròn nét đứt cách nhau 1m tính từ radar
- **Trục Y chính**: Nét đứt màu xanh, chỉ hướng thẳng từ radar
- **Radar**: Vị trí radar ở giữa cạnh dưới (điểm đỏ, ghi chữ RADAR)
- **Hiệu ứng quét**: Cảm giác radar thực tế, có thể tắt trong công cụ
- **Zones (3 hình chữ nhật)**:
- Zone 1 (Xanh lá), Zone 2 (Cam), Zone 3 (Đỏ)
- Có người → Màu đậm, nét liền, badge `CÓ NGƯỜI`
- Trống → Màu nhạt, nét đứt, badge `TRỐNG`
- **Targets (3 điểm)**:
- Màu: Vàng (Slot 1), Tím (Slot 2), Xanh (Slot 3)
- Điểm sáng có viền trắng, bao quanh là hiệu ứng glow
- Mũi tên: Tốc độ (độ dài tỉ lệ với cm/s, hướng theo chuyển động)
- Nhãn: `T# | X,Y mm | tốc độ cm/s`
- Đường dẫn (trail): Mờ dần theo thời gian (tối đa 3.5s)
- Target vừa mất dấu: Hiển thị viền đứt mờ trong 3.5s nữa
### 4.4 Công cụ trên Canvas (bên phải tiêu đề)
- ✅ **Hiệu ứng quét** — Bật/tắt hiệu ứng quét radar
- ✅ **Đường dẫn target** — Bật/tắt trail fade
- ✅ **Hiển thị label** — Bật/tắt nhãn trên zones & targets
### 4.5 Panel bên phải
**Thống kê:**
- Frames nhận (tổng cộng)
- Cập nhật cách đây bao nhiêu giây
- Target hợp lệ / 3
- Vùng có người / 3
**Chi tiết Target:**
- Mỗi target có một thẻ màu, hiển thị:
- Trạng thái: `Đã phát hiện` / `Vừa mất dấu` / `Trống`
- `X, Y (mm)`
- `Tốc độ (cm/s)`
- `Khoảng cách (m)`
- `Góc (°)` — 0° = thẳng, âm = trái, dương = phải
**Zones:**
- Danh sách 3 zones với tọa độ
- Nút **Sửa zones** mở form chỉnh sửa từng toạ độ X1/Y1/X2/Y2 (mm)
- **Mặc định**: Khôi phục layout gốc
- **Hủy**: Bỏ sửa
- **Lưu & Áp dụng**: Gửi cập nhật POST `/api/zones` → forward tới ESP32 `/updateZones`, hiển thị toast thành công
---
## 5. Kiến trúc & luồng dữ liệu
```
┌───────────┐ WebSocket /ws ┌──────────────────┐ WebSocket /ws ┌──────────────┐
│ ESP32-S3 │ ◄──────────────────► │ Orange Pi (WS │ ◄──────────────────► │ Trình duyệt │
│ LD2453 │ JSON frame radar │ Server FastAPI) │ JSON state đồng bộ │ (Canvas 2D) │
└───────────┘ └──────────────────┘ └──────────────┘
▲ ▲ ▲
│ REST GET /zones │ GET / → trả về index.html │
│ REST POST /updateZones │ GET /static/* → CSS, JS │
└───────────────────────────────────┘ REST API /api/* │
Visualizer tự động gọi ESP32 khi bạn chỉnh zones trên web │
```
**Các endpoint chính:**
| Method | Endpoint | Mục đích |
|---|---|---|
| GET | `/` | Trả về trang web chính |
| GET | `/api/state` | Lấy trạng thái radar hiện tại (JSON) |
| GET | `/api/zones` | Lấy danh sách zones |
| POST | `/api/zones` | Cập nhật zones (forward tới ESP32 `/updateZones`) |
| WS | `/ws` | Endpoint WebSocket cho trình duyệt (broadcast realtime) |
---
## 6. Tùy chỉnh & chạy nền
### 6.1 Chạy nền bằng tmux
```bash
tmux new -s radar
cd ~/orangepi-server && source venv/bin/activate
python3 visualizer.py 192.168.1.100
# Ctrl+B D → detach (thoát ra mà vẫn chạy)
# tmux attach -t radar → quay lại session
```
### 6.2 systemd service (tự chạy khi boot Orange Pi)
Tạo file `/etc/systemd/system/radar-web.service`:
```ini
[Unit]
Description=LD2453 Radar Web Visualizer
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=orangepi
WorkingDirectory=/home/orangepi/orangepi-server
ExecStart=/home/orangepi/orangepi-server/venv/bin/python3 /home/orangepi/orangepi-server/visualizer.py 192.168.1.100 --port 8000
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
```
Sau đó:
```bash
sudo systemctl daemon-reload
sudo systemctl enable radar-web
sudo systemctl start radar-web
journalctl -u radar-web -f # Xem log realtime
```
### 6.3 Tùy chỉnh giao diện
- **Màu sắc & chủ đề**: chỉnh các biến CSS ở đầu file `static/style.css` (định nghĩa `--z1`, `--t1`, `--bg`…)
- **Range hiển thị**: chỉnh hằng `MAP.X_MIN/X_MAX/Y_MIN/Y_MAX` trong `static/app.js`
- **Mặc định zones**: sửa `DEFAULT_ZONES` trong `static/app.js` và `STATE.zones[0..2]` trong `visualizer.py`
---
## 7. Troubleshooting — Kiểm tra lỗi nhanh
### 7.1 Quick check trong 60 giây
Nếu backend log "Đã kết nối WebSocket tới ESP32" nhưng web không thấy dữ liệu, làm theo thứ tự:
1. **Mở DevTools (F12) → tab Console**:
- Gõ: `await fetch('/api/state').then(r=>r.json()).then(d=>console.log(d))`
- Kết quả đúng phải là object có `zones` và `targets` (3 phần tử mỗi mảng)
- Nếu nhận được object: WebSocket frontend đang có vấn đề (hãy xem log có chữ `[Radar] WebSocket closed?`)
- Nếu không nhận được: backend có vấn đề (xem log terminal backend)
2. **Xem 2 chip trạng thái trên header (góc trên phải)**:
- `ESP32: Đã kết nối` (xanh) — backend ↔ ESP32 OK
- `WS: Đã kết nối` (xanh) — trình duyệt ↔ backend OK
- Nếu WS màu đỏ: mở DevTools → Network → WS → chọn `/ws` → xem "Messages" có dòng nào không.
3. **Hiển thị gì phải có ngay cả khi parser ESP32 là stub?**
- Grid, vòng tròn khoảng cách, radar (điểm đỏ dưới cùng), **3 zones theo mặc định phải hiện rõ ràng**
- Chip `ESP32` phải xanh, `Frames` trong Thống kê = 0 là bình thường (parser stub)
- **Target không thấy là Bình thường** nếu parser chưa implement — nhưng zones & grid PHẢI hiện.
---
### 7.2 Không mở được trang web
1. **Ping Orange Pi**: `ping <OrangePi_IP>`
2. **Kiểm tra port đang mở**: `ss -tulpn | grep 9000` (thay `9000` bằng port bạn dùng)
3. **Tường lửa?** `sudo ufw allow 9000/tcp` hoặc tạm thời `sudo ufw disable`
4. **Visualizer đang chạy?** `ps aux | grep visualizer`
---
### 7.3 Không thấy dữ liệu radar / không có target
1. **Kiểm tra 2 chip trạng thái** (section 7.1)
2. **Ping ESP32**: `ping 192.168.88.32` (thay IP của bạn)
3. **Test REST ESP32**: `curl http://192.168.88.32/status` → phải trả JSON `{"status":"ok",...}`
4. **Test state từ backend (ngoài trình duyệt)**: `curl http://<OrangePi_IP>:9000/api/state` → object JSON 2 mảng zones + targets
5. **ESP32 đang `DIAGNOSTIC_MODE 1`?** → phải là `0` mới có WiFi/WebSocket
6. **Parser ESP32 vẫn là stub?** → **Target luôn = 0 / INVALID là BÌNH THƯỜNG** cho đến khi bạn implement `tryParseFrame()` thật trong `LD2453Parser.cpp`. Grid + zones vẫn phải hiện.
---
### 7.4 Tốc độ cập nhật chậm
- Mặc định radar LD2453 gửi ~2 frame/giây. Đó là giới hạn phần cứng.
- **Bảo đảm Orange Pi gửi state liên tục**: heartbeat 1 giây luôn gửi ngay cả khi ESP32 không gửi frame mới.
- Đảm bảo Orange Pi và ESP32 có tín hiệu WiFi tốt.
---
### 7.5 WebSocket không kết nối (trình duyệt báo lỗi)
- Mở DevTools (F12) → **Console** sẽ log rõ ràng: `[Radar] WebSocket closed (code=...)`
- `code=1006`: mạng bị ngắt giữa chừng → tự động reconnect 2s sau
- `code=403/404`: mở sai port hoặc sai path
- Kiểm tra trình duyệt truy cập đúng `http://<IP>:9000` (**KHÔNG** `https://`)
- Nếu mở trên **mobile cùng WiFi** mà không được: tắt "VPN / Private DNS / Use random MAC" trên điện thoại thử lại
- Nếu dùng reverse proxy (Caddy/Nginx): cấu hình WebSocket upgrade (header `Upgrade: websocket`)
---
## 8. So sánh với bản cũ (Tkinter/Pygame)
| Đặc điểm | Tkinter cũ | **Web Visualizer mới** |
|---|---|---|
| Cần màn hình gắn Orange Pi? | ✅ Có | ❌ **Không** (xem trên trình duyệt bất kỳ đâu) |
| Xem trên điện thoại? | ❌ Không | ✅ **Có** |
| Nhiều người xem cùng lúc? | ❌ 1 người | ✅ **Không giới hạn** (multi-client) |
| Đồ họa đẹp? | ⭐⭐ cơ bản | ⭐⭐⭐⭐⭐ **Canvas2D + hiệu ứng + sáng/tối** |
| Chỉnh sửa zones? | Edit code Python | ✅ **Form trên web** |
| Song ngữ VI/EN? | ❌ Không | ✅ **Có** |
| Responsive mobile? | ❌ Không | ✅ **Có** |
---
---
# 🇬🇧 English Version
# ESP32-S3 LD2453 Radar — Orange Pi Web Visualizer
Installation and usage guide for the **Web Visualizer** on Orange Pi (or any machine with Python 3.8+) to display realtime radar data via a beautiful web interface — **no Tkinter/Pygame desktop required anymore**.
---
## 1. Requirements
- Orange Pi / Raspberry Pi / Any PC with **Python 3.8+**
- Same LAN as the ESP32 (Wi-Fi or Ethernet)
- ESP32 IP address known (check Serial Monitor at boot, or router admin)
### 1.1 Folder structure
```
orangepi-server/
├── visualizer.py # FastAPI server: connects to ESP32 + broadcasts to browsers
├── requirements.txt # Python dependencies
├── templates/
│ └── index.html # Web UI HTML
└── static/
├── style.css # Styling: light/dark, responsive, bilingual
└── app.js # Canvas renderer + browser WebSocket client
```
### 1.2 Copy files to Orange Pi
Copy the whole `orangepi-server/` folder to the Orange Pi. SCP example:
```bash
scp -r orangepi-server/ orangepi@<ORANGE_PI_IP>:/home/orangepi/
```
---
## 2. Install Python dependencies
```bash
cd ~/orangepi-server
# (Recommended) Use a virtual env
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
```
**Global install (faster, not recommended):**
```bash
pip3 install -r requirements.txt
```
> No `python3-tk` needed — everything runs in the browser.
---
## 3. Usage
### 3.1 Flash ESP32 in Full Stage mode
In `config.h` (Arduino IDE):
```cpp
#define DIAGNOSTIC_MODE 0 // Full mode: WiFi + WebSocket + REST API
```
Flash the code, grab the ESP32 IP from Serial Monitor. Example: `192.168.1.100`.
### 3.2 Start the Web Visualizer on Orange Pi
```bash
source venv/bin/activate # if using venv
python3 visualizer.py 192.168.1.100
```
CLI options:
```bash
# Custom port (default 8000):
python3 visualizer.py 192.168.1.100 --port 8080
# Restrict to localhost only:
python3 visualizer.py 192.168.1.100 --host 127.0.0.1
# Verbose logging:
python3 visualizer.py 192.168.1.100 --verbose
```
The console will print:
```
==============================================================
ESP32-S3 LD2453 Radar — Web Visualizer
==============================================================
ESP32 : ws://192.168.1.100/ws
Web UI : http://0.0.0.0:8000/
WS UI : ws://<OrangePi_IP>:8000/ws
==============================================================
```
### 3.3 Open the web UI on any device
On **any device on the same LAN** (phone, laptop, desktop…) open:
```
http://<OrangePi_IP>:8000/
```
Example: if Orange Pi IP is `192.168.1.50` → `http://192.168.1.50:8000/`
---
## 4. Web UI features
### 4.1 Highlights
| Feature | Description |
|---|---|
| 🎨 **Beautiful UI** | Light/Dark themes with Catppuccin color palette |
| 🌐 **Bilingual** | Vietnamese / English toggle in header |
| 📱 **Responsive** | Looks great on mobile, tablet, desktop |
| 🧭 **Radar Canvas** | Grid, concentric range rings, sweep animation |
| 🎯 **Targets** | Up to 3 colored targets, trails, velocity arrows, labels |
| 🟩 **Zones** | 3 rectangular zones with occupancy state badges |
| 📊 **Stats** | Frames received, valid targets, occupied zones, latency |
| 📝 **Details** | Per-target and per-zone info in the right panel |
| ✏️ **Zone editor** | Edit zone coordinates directly in the UI → pushes to ESP32 |
| 🔗 **Multi-client** | Any number of browser tabs/screens stay in sync |
### 4.2 Header
- 🟢/🟡/🔴 ESP32 + WebSocket connection status chips
- ☀️/🌙 Theme toggle (Light/Dark)
- `VI / EN` Language toggle
### 4.3 Radar View (Canvas)
- **Grid**: 1m cells with labels (−5m ← 0 → +5m horizontal, 0 → 7m forward)
- **Range rings**: Dashed concentric arcs every 1m from the radar
- **Radar dot**: Red circle at bottom-center with "RADAR" label
- **Sweep effect**: Rotating semi-transparent cone (toggleable)
- **Zones (3 rectangles)**:
- Z1 (Green), Z2 (Orange), Z3 (Red)
- Occupied → solid fill, bold border, `OCCUPIED` badge
- Empty → faint fill, dashed border, `EMPTY` badge
- **Targets (3 slots)**:
- Colors: Yellow (S1), Purple (S2), Cyan (S3)
- Glowing dots with white outline
- Velocity arrow (length ∝ cm/s, points along direction)
- Label: `T# | X,Y mm | speed cm/s`
- Fading trail for each target, persists ~3.5s
- Recently-lost targets show as dashed ghost circles
### 4.4 Tool toggles (top-right of canvas)
- Sweep effect on/off
- Target trails on/off
- Zone/target labels on/off
### 4.5 Side panels (right column)
**Statistics:**
- Total frames received
- Seconds since last frame
- Valid targets / 3
- Occupied zones / 3
**Target details:**
- Colored cards per target with status badge (`Detected`/`Recently seen`/`Free`)
- X/Y (mm), Speed (cm/s), Distance (m), Angle (°)
**Zones:**
- Per-zone list with coordinates and status
- **Edit zones** button opens a form (X1/Y1/X2/Y2 in mm):
- *Reset defaults* → reload original layout
- *Cancel* → close without saving
- *Save & Apply* → POST to `/api/zones` → forwarded to ESP32 `/updateZones` with success toast
---
## 5. Architecture & data flow
```
┌───────────┐ WebSocket /ws ┌──────────────────┐ WebSocket /ws ┌──────────────┐
│ ESP32-S3 │ ◄──────────────────► │ Orange Pi (WS │ ◄──────────────────► │ Browser │
│ LD2453 │ JSON radar frames │ FastAPI Server) │ synced state JSON │ (Canvas 2D) │
└───────────┘ └──────────────────┘ └──────────────┘
▲ ▲ ▲
│ REST /zones /updateZones │ GET / → index.html │
└───────────────────────────────────┘ GET /static/* → CSS, JS │
Zone edits in the UI are forwarded to the ESP32 │
```
**Endpoints:**
| Method | Endpoint | Purpose |
|---|---|---|
| GET | `/` | Main web page |
| GET | `/api/state` | Current radar state (fallback poll) |
| GET | `/api/zones` | Zones list |
| POST | `/api/zones` | Update zones (forwarded to ESP32 `/updateZones`) |
| WS | `/ws` | Realtime broadcast for browser clients |
---
## 6. Background running & customization
### 6.1 Run with tmux
```bash
tmux new -s radar
cd ~/orangepi-server && source venv/bin/activate
python3 visualizer.py 192.168.1.100
# Ctrl+B D → detach; tmux attach -t radar to return
```
### 6.2 systemd service (auto-start at boot)
Create `/etc/systemd/system/radar-web.service`:
```ini
[Unit]
Description=LD2453 Radar Web Visualizer
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=orangepi
WorkingDirectory=/home/orangepi/orangepi-server
ExecStart=/home/orangepi/orangepi-server/venv/bin/python3 /home/orangepi/orangepi-server/visualizer.py 192.168.1.100 --port 8000
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
```
Then:
```bash
sudo systemctl daemon-reload
sudo systemctl enable radar-web
sudo systemctl start radar-web
journalctl -u radar-web -f # Live logs
```
### 6.3 Customization tips
- **Colors/themes**: edit CSS variables at top of `static/style.css` (`--z1`, `--t1`, `--bg`…)
- **Display range**: tweak `MAP.X_MIN/X_MAX/Y_MIN/Y_MAX` constants in `static/app.js`
- **Default zones**: modify `DEFAULT_ZONES` in `static/app.js` and `STATE.zones[0..2]` in `visualizer.py`
---
## 7. Troubleshooting
### Can't open the web page
1. Ping Orange Pi: `ping <OrangePi_IP>`
2. Check open port: `ss -tulpn | grep 8000`
3. Firewall? `sudo ufw allow 8000/tcp`
4. Is visualizer running? `ps aux | grep visualizer`
### No radar targets visible
1. Check ESP32 connection status chip (top-right corner of UI)
2. Ping ESP32: `ping 192.168.1.100`
3. Test ESP32 REST: `curl http://192.168.1.100/status` → JSON expected
4. ESP32 still in `DIAGNOSTIC_MODE 1`? → set to `0` for WiFi/WebSocket
5. ESP32 parser is still stubbed? → Targets always `INVALID` until real byte parser is implemented (see main repo README)
### Slow UI updates
- LD2453 radar sends ~2 frames/second by design (hardware limit)
- Ensure good Wi-Fi signal on both ESP32 and Orange Pi
### Browser WebSocket fails to connect
- Open DevTools → Console for the exact error
- Make sure you visit `http://<IP>:8000` (not `https://`)
- If behind Caddy/Nginx: enable WebSocket upgrade headers (`Upgrade: websocket`)
---
## 8. Comparison with old Tkinter/Pygame version
| Aspect | Old Tkinter | **New Web Visualizer** |
|---|---|---|
| Requires a monitor on Orange Pi? | Yes | ❌ **No** — view anywhere |
| View on phone? | No | ✅ **Yes** |
| Multiple viewers? | No | ✅ **Unlimited clients** |
| Graphics quality | Basic ⭐⭐ | **Canvas + effects + themes ⭐⭐⭐⭐⭐** |
| Zone editor | Edit Python | ✅ **In-UI form** |
| i18n (VI/EN) | No | ✅ **Built-in** |
| Mobile responsive | No | ✅ **Yes** |