first commit

This commit is contained in:
2026-03-06 22:19:58 +07:00
commit 9771033418
13 changed files with 2141 additions and 0 deletions

255
README.md Normal file
View File

@@ -0,0 +1,255 @@
# ⚡ IoT Firmware Loader
Công cụ desktop dùng để **scan, phát hiện và flash firmware hàng loạt** cho các thiết bị IoT (ESP32/ESP8266) trong mạng LAN.
> **Tech stack:** Python 3.9+ · PyQt6 · Scapy · Requests
---
## 📁 Cấu trúc dự án
```
iot_fw_loader/
├── main.py # UI chính (PyQt6) + điều phối toàn bộ luồng
├── scanner.py # Quét thiết bị trong mạng LAN
├── device_filter.py # Lọc thiết bị IoT theo MAC vendor
├── flasher.py # Upload firmware qua HTTP
├── run.sh # Script khởi chạy (macOS/Linux)
├── run.bat # Script khởi chạy (Windows)
└── venv/ # Python virtual environment
```
---
## 🔧 Cài đặt & Chạy
### Yêu cầu
- Python 3.9+
- Các thư viện: `PyQt6`, `scapy`, `requests`
### Khởi chạy nhanh
**macOS / Linux:**
```bash
./run.sh
```
**Windows:**
```bat
run.bat
```
> Script tự tạo `venv` và cài dependencies nếu chưa có.
**Hoặc chạy thủ công:**
```bash
source venv/bin/activate
python main.py
```
---
## 🏗 Kiến trúc hệ thống
```
┌─────────────────────────────────────────────────────┐
│ main.py (UI) │
│ ┌───────────┐ ┌───────────┐ ┌────────────────────┐ │
│ │ Machine │ │ Firmware │ │ Network Scan │ │
│ │ Info │ │ Selector │ │ (QThread) │ │
│ └───────────┘ └───────────┘ └────────┬───────────┘ │
│ ┌────────────────────────────────────┼───────────┐ │
│ │ Device Table │ │ │
│ │ IP │ MAC │ Type │ Status │ │ │
│ └────────────────────────────────────┼───────────┘ │
│ ┌────────────────────────────────────┼───────────┐ │
│ │ Flash Controls + Progress Bar │ │ │
│ │ (ThreadPoolExecutor × 5) │ │ │
│ └────────────────────────────────────┼───────────┘ │
└───────────────────────────────────────┼─────────────┘
┌───────────────────┼───────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ scanner.py │ │device_filter│ │ flasher.py │
│ │ │ .py │ │ │
│ Scapy ARP │ │ MAC vendor │ │ HTTP POST │
│ ↓ fallback│ │ matching │ │ /update │
│ arp -a │ │ │ │ │
└────────────┘ └─────────────┘ └─────────────┘
```
---
## 🔄 Luồng hoạt động chi tiết
### Tổng quan
```
Khởi động App ──▶ Hiển thị Machine Info
Chọn Firmware (.bin/.hex/.uf2)
Nhập dải mạng ──▶ Nhấn "Scan LAN"
┌─────────────────────────────────┐
│ ScanThread (background) │
│ 1. Thử Scapy ARP broadcast │
│ 2. Nếu lỗi → fallback arp -a │
└──────────────┬──────────────────┘
Lọc IoT device theo MAC vendor
Hiển thị bảng: tất cả thiết bị
(IoT = 🟢 xanh lá, Other = ⚪)
Nhấn "Flash All IoT Devices"
┌──────────────────────────────────┐
│ ThreadPoolExecutor (5 workers) │
│ POST firmware → mỗi thiết bị │
│ Cập nhật status + progress bar │
└──────────────────────────────────┘
Hoàn tất ✅
```
---
## 📋 Phân tích từng module
### 1. `main.py` — Giao diện & Điều phối
| Thành phần | Mô tả |
| ------------------------- | --------------------------------------------- |
| `get_local_ip()` | Lấy IP máy tính qua UDP socket tới `8.8.8.8` |
| `get_default_network(ip)` | Tự tính dải mạng `/24` từ IP máy |
| `get_machine_info()` | Thu thập hostname, IP, OS, MAC |
| `ScanThread` | `QThread` chạy scan ở background, tránh đơ UI |
| `App` | Widget chính chứa toàn bộ UI |
**Các phần UI:**
| Group Box | Chức năng |
| ---------------- | -------------------------------------- |
| 🖥 Machine Info | Hiển thị Hostname, IP, OS, MAC của máy |
| 📦 Firmware | Chọn file firmware |
| 📡 Network Scan | Nhập dải mạng + nút Scan |
| 📋 Devices Found | Bảng thiết bị (IP, MAC, Type, Status) |
| 🚀 Flash | Progress bar + nút Flash All |
**Xử lý sự kiện chính:**
| Method | Trigger | Hành động |
| ------------------ | ------------------ | ------------------------------------------ |
| `select_fw()` | Nhấn "Select File" | Mở dialog chọn firmware |
| `scan()` | Nhấn "Scan LAN" | Tạo `ScanThread` → chạy background |
| `_on_scan_done()` | Scan hoàn tất | Phân loại IoT/Other → hiển thị bảng |
| `_on_scan_error()` | Scan lỗi | Hiện `QMessageBox` lỗi |
| `flash_all()` | Nhấn "Flash All" | Tạo `ThreadPoolExecutor` → flash song song |
| `flash_worker()` | Mỗi thread | POST firmware → cập nhật status |
---
### 2. `scanner.py` — Quét mạng
**Chiến lược 2 lớp (dual-layer scan):**
| Phương pháp | Hàm | Yêu cầu | Độ chính xác |
| ----------------------- | ------------------------ | -------------- | ------------------------------------------- |
| **Primary:** Scapy ARP | `_scan_with_scapy()` | Root/sudo | Cao — scan active |
| **Fallback:** ARP table | `_scan_with_arp_table()` | Không cần root | Trung bình — chỉ thấy thiết bị đã giao tiếp |
**Luồng trong `scan_network()`:**
```python
try:
return _scan_with_scapy(network) # Cần root
except:
return _scan_with_arp_table(network) # Fallback, parse "arp -a"
```
**Scapy ARP scan:**
- Tạo ARP request → broadcast `ff:ff:ff:ff:ff:ff`
- Thu thập response → trích IP + MAC
- Timeout: 2 giây
**ARP table fallback:**
- Chạy lệnh `arp -a` của hệ thống
- Parse output bằng regex: `\(IP\) at MAC`
- Lọc theo dải mạng đầu vào
---
### 3. `device_filter.py` — Phân loại thiết bị
Lọc thiết bị IoT dựa trên **3 byte đầu của MAC address** (OUI — Organizationally Unique Identifier):
| MAC Prefix | Vendor |
| ---------- | ------------------------- |
| `24:6F:28` | Espressif (ESP32/ESP8266) |
| `84:F3:EB` | Espressif |
| `DC:4F:22` | Espressif |
> **Lưu ý:** Chỉ thiết bị có MAC bắt đầu bằng các prefix trên mới được đánh dấu là IoT. Tất cả thiết bị khác vẫn hiển thị trong bảng với label "Other".
---
### 4. `flasher.py` — Nạp firmware
| Bước | Chi tiết |
| ------------- | --------------------------------------------------- |
| 1. Mở file | `open(firmware_path, "rb")` |
| 2. Upload | `POST http://{ip}/update` với `multipart/form-data` |
| 3. Chờ reboot | `time.sleep(3)` — đợi thiết bị khởi động lại |
| 4. Kết quả | Trả `"DONE"` hoặc `"FAIL"` |
> **Giả định:** Thiết bị IoT chạy HTTP server với endpoint `/update` hỗ trợ OTA firmware update (chuẩn ESP32 Arduino OTA).
---
## ⚙️ Cấu hình
| Tham số | Giá trị | Vị trí |
| ----------------- | ----------------------- | ----------------------------------------------- |
| Network range | Tự tính `/24` từ IP máy | `main.py``get_default_network()` |
| Scan timeout | 2 giây | `scanner.py``srp(..., timeout=2)` |
| Flash timeout | 10 giây | `flasher.py``requests.post(..., timeout=10)` |
| Reboot wait | 3 giây | `flasher.py``time.sleep(3)` |
| Max flash workers | 5 | `main.py``ThreadPoolExecutor(max_workers=5)` |
| Firmware filter | `.bin .hex .uf2` | `main.py``QFileDialog` filter |
---
## 🔒 Lưu ý bảo mật & Quyền
- **Scapy cần root/sudo** trên macOS/Linux để gửi raw ARP packets
- Nếu không có quyền root → tự động dùng `arp -a` (không cần root nhưng chỉ thấy thiết bị đã giao tiếp gần đây)
- Firmware upload qua **HTTP không mã hóa** — chỉ nên dùng trong mạng LAN nội bộ
---
## 📊 Trạng thái thiết bị (State Machine)
```
READY ──────▶ FLASHING
│ │
│ ┌─────┴──────┐
│ ▼ ▼
│ ✅ DONE ❌ FAIL
└── (thiết bị Other: trạng thái "—", không flash)
```