first commit
This commit is contained in:
255
README.md
Normal file
255
README.md
Normal 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)
|
||||
```
|
||||
Reference in New Issue
Block a user