11 KiB
11 KiB
⚡ 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:
./run.sh
Windows:
run.bat
Script tự tạo
venvvà cài dependencies nếu chưa có.
Hoặc chạy thủ công:
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():
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 -acủ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
/updatehỗ 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)