Files
Mira_Firmware_Loader/README.md
2026-03-12 23:55:26 +07:00

16 KiB
Raw Permalink Blame History

Mira 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ị OpenWrt trong mạng LAN.
Hỗ trợ nạp thủ công (chọn thiết bị → flash) và tự động hóa (scan → phát hiện → flash → retry).

Tech stack: Python 3.9+ · PyQt6 · Paramiko/SCP · Requests · PyInstaller
Phiên bản: 1.2.0


📁 Cấu trúc dự án

Mira_Firmware_Loader/
├── main.py                    # UI chính (PyQt6) — App + AutoFlashWindow
├── version.txt                # Số phiên bản ứng dụng
├── requirements.txt           # Danh sách dependencies
├── core/
│   ├── scanner.py             # Quét thiết bị mạng (Ping sweep đa luồng)
│   ├── workers.py             # ScanThread — chạy scanner trong background thread
│   ├── api_flash.py           # Flash firmware qua LuCI HTTP API
│   ├── ssh_utils.py           # SSH/SCP transport helpers dùng chung
│   ├── ssh_new_flash.py       # Luồng SSH cho chế độ Nạp Mới (Telnet → set passwd → SSH)
│   ├── ssh_update_flash.py    # Luồng SSH cho chế độ Update (SSH trực tiếp)
│   ├── flash_new_worker.py    # NewFlashThread  — QThread điều phối Nạp Mới FW
│   ├── flash_update_worker.py # UpdateFlashThread — QThread điều phối Update FW
│   └── auto_flash_worker.py   # AutoFlashWorker — QThread tự động scan → flash → retry
├── ui/
│   ├── components.py          # Custom Qt Widgets (CollapsibleGroupBox)
│   └── styles.py              # Stylesheet toàn ứng dụng (STYLE + AUTO_STYLE)
├── utils/
│   ├── network.py             # Helper IP / network (get_local_ip, get_default_network)
│   └── system.py              # Lấy thông tin máy, resource path cho PyInstaller
├── docs/
│   ├── api_flash_docs.md      # Tài liệu kỹ thuật LuCI API flash
│   ├── load_fw_ssh_docs.md    # Tài liệu kỹ thuật SSH flash (cả 2 luồng)
│   ├── scanner_docs.md        # Tài liệu kỹ thuật scanner
│   └── auto_flash_docs.md     # Tài liệu kỹ thuật tự động hóa nạp FW
├── run.sh                     # Script khởi chạy (macOS/Linux)
├── run.bat                    # Script khởi chạy (Windows)
└── build_windows.bat          # Script đóng gói thành .exe (Windows, PyInstaller)

🔧 Cài đặt & Chạy

Yêu cầu

  • Python 3.9+
  • Thư viện: PyQt6, requests, paramiko, scp, pyinstaller (để build)

Khởi chạy nhanh

macOS / Linux:

./run.sh

Windows:

run.bat

Script tự tạo venv và cài dependencies nếu chưa có.

📦 Build file chạy độc lập (.exe) cho Windows

build_windows.bat

Output: dist\Mira_Firmware_Loader.exe — không cần cài Python trên máy đích.


🏗 Kiến trúc hệ thống

┌──────────────────────────────────────────────────────────┐
│                        main.py (UI)                      │
│                                                          │
│  Machine Info │ Firmware Selector │ Network Scan         │
│  ─────────────────────────────────────────────────────   │
│  Device Table  [ ] IP  │  MAC  │  Status                 │
│  ─────────────────────────────────────────────────────   │
│  Flash Controls                                          │
│    Flash Mode: [ New Flash | Update Firmware ]           │
│    Method:     [ API (LuCI) | SSH (paramiko) ]           │
│    Concurrent devices: [SpinBox]                         │
│    [ ⚡ FLASH SELECTED DEVICES ]                          │
│  ─────────────────────────────────────────────────────   │
│  [ 🤖 Tự động hóa nạp FW ]                               │
└────────────────────────┬─────────────────────────────────┘
                         │
       ┌─────────────────┼─────────────────┐
       │                 │                 │
┌──────▼──────┐  ┌───────▼──────────┐  ┌───▼──────────────────┐
│  scanner.py │  │  Flash Workers   │  │  AutoFlashWorker     │
│             │  │  (thủ công)      │  │  (tự động hóa)       │
│  Ping Sweep │  │                  │  │                      │
│ (Multithread│  │  NewFlashThread  │  │  Phase 1: Scan LAN   │
│  gọi  OS    │  │  ├─ api_flash    │  │    (tối đa 15 lần)   │
│  ping cmd)  │  │  └─ ssh_new_flash│  │  Phase 2: Flash      │
│             │  │                  │  │    (auto-retry x3)   │
└─────────────┘  │  UpdateFlash     │  │  ├─ api_flash        │
                 │  Thread          │  │  └─ ssh_new_flash    │
                 │  └─ ssh_update   │  └──────────────────────┘
                 └──────────────────┘
                          │
               ┌──────────▼──────────┐
               │     ssh_utils.py    │
               │  (Transport Layer)  │
               │  _create_ssh_client │
               │  _upload_firmware   │
               │  _verify_firmware   │
               │  _sync_and_sysupgr  │
               └─────────────────────┘

🔄 Luồng hoạt động chi tiết

1. Quét mạng (Network Scan)

scanner.py sử dụng chiến lược Ping trước → ARP sau, đảm bảo vừa lấy được MAC vừa loại bỏ hoàn toàn lỗi thiết bị ảo do ARP cache cũ:

Giai đoạn Mô tả
Ping Sweep Gửi ping đồng thời tới toàn bộ host trong dải mạng bằng tiến trình con (đa luồng). Nhằm xác định chính xác thiết bị nào đang thực sự cấp nguồn online.
ARP Lookup Ngay sau khi ping, query ARP table hệ điều hành (song song) chỉ cho các IP vừa ping thành công, lấy MAC Address chuẩn xác trước khi cache cũ hết hạn.

Kết quả (IP và MAC) được sort tăng dần theo IP trước khi trả về UI.

2. Bảng thiết bị (Device Table)

  • Hiển thị cột: checkbox, IP, MAC, Status
  • Mặc định ẩn gateway và IP máy tính đang chạy (có thể bật "Show all")
  • Thiết bị đã flash trong session được đánh dấu "Already Flashed" và tự bỏ tick

3. Flash Firmware (Thủ công)

Có 2 chế độ và 2 method, tổng cộng 3 luồng thực thi khác nhau:

Chế độ New Flash — dùng cho thiết bị vừa reset cứng

Method Luồng Mô tả
API (LuCI) api_flash.py Đăng nhập LuCI → upload firmware → Proceed. Hỗ trợ Barrier Breaker 14.07 và OpenWrt mới hơn
SSH ssh_new_flash.py Kết nối Telnet → đặt password mới → SSH → SCP upload → sysupgrade

Luồng SSH New Flash chi tiết:

  1. Telnet port 23 (thiết bị vừa reset, chưa có pass)
  2. Đặt password admin123a qua lệnh passwd
  3. SSH vào với password vừa đặt
  4. SCP upload firmware lên /tmp/
  5. Verify file tồn tại
  6. sync && sysupgrade -F -v -n → thiết bị reboot (connection drop = DONE)

Chế độ Update Firmware — dùng cho thiết bị đang chạy OpenWrt

Luồng ssh_update_flash.py, SSH trực tiếp (không qua Telnet):

  1. SSH kết nối với root / admin123a (fallback: admin)
  2. SCP upload firmware lên /tmp/
  3. Verify file tồn tại
  4. sync && sysupgrade -F -v -n → thiết bị reboot

⚠️ Update Mode hiển thị cảnh báo nếu IP thiết bị khác 192.168.11.102 và yêu cầu xác nhận.

4. 🤖 Tự động hóa nạp FW (MỚI)

Tính năng nạp FW hoàn toàn tự động — chỉ cần cấu hình và nhấn bắt đầu:

Cấu hình → Auto Scan LAN → Phát hiện đủ thiết bị → Auto Flash → Auto Retry → Thông báo

4.1. Quy trình

Phase Mô tả
Phase 1: Scan LAN Scan mạng liên tục mỗi 5 giây, tối đa 15 lần, cho đến khi đủ thiết bị
Phase 2: Flash Nạp FW song song qua ThreadPoolExecutor, tự động retry tối đa 3 lần

4.2. Cấu hình

Tham số Mô tả Mặc định
Firmware File firmware (.bin/.hex/.uf2) Lấy từ cửa sổ chính
Mạng Dải mạng LAN cần scan Tự suy từ IP host
Số lượng Số thiết bị cần nạp 5
Phương thức API (LuCI) hoặc SSH API (LuCI)
Song song Số thiết bị nạp cùng lúc (0 = không giới hạn) 10

4.3. Auto-Retry nạp FW

Khi một thiết bị nạp thất bại, hệ thống tự động retry:

Lần 1 → FAIL: Connection timeout
         ⚠️ Log cảnh báo, chờ 2 giây...
Lần 2 → FAIL: Upload error
         ⚠️ Log cảnh báo, chờ 2 giây...
Lần 3 → DONE ✅ (hoặc ❌ báo lỗi sau 3 lần)
  • Tối đa 3 lần retry mỗi thiết bị (MAX_FLASH_RETRIES)
  • Chờ 2 giây giữa mỗi lần retry để thiết bị ổn định
  • Nếu hết 3 lần vẫn fail → đánh dấu , tiếp tục thiết bị tiếp theo

4.4. Scan Timeout

  • Nếu scan 15 lần mà chưa đủ thiết bị → dừng và hiện cảnh báo
  • Gợi ý kiểm tra: thiết bị đã bật chưa, dải mạng có đúng không

4.5. Quy tắc bảo vệ IP 192.168.11.102

Chế độ Được nạp 192.168.11.102? Cơ chế
New Flash (thủ công) Không Chặn trước khi flash, hiện cảnh báo
Update FW (thủ công) Cho phép bình thường
Tự động hóa Không Tự động lọc khỏi kết quả scan

4.6. Lịch sử nạp

Kết quả nạp được lưu ở 2 cấp:

Nơi lưu Phạm vi Format
AutoFlashWindow._auto_history Phiên tự động hiện tại list[(ip, mac, result, timestamp)]
App.flashed_macs Toàn bộ session app dict{MAC: (ip, mac, result, ts)}
  • Cả thành công lẫn thất bại đều được ghi lại
  • Nút "📋 Lịch sử nạp" hiển thị cùng format ở cả 2 cửa sổ: [HH:MM:SS] ✅/❌ IP (MAC) — result

5. Xử lý song song

Tham số Mô tả
Concurrent devices Số thiết bị flash đồng thời (ThreadPoolExecutor). 0 = không giới hạn

⚙️ Cấu hình mặc định

Tham số Giá trị mặc định Mô tả
Network Tự suy ra từ local IP (x.x.x.0/24) Dải mạng để quét
Flash Mode New Flash Nạp mới hoặc Update
Method API (LuCI) Phương thức flash cho New Flash
SSH User root Hardcoded, không hiển thị trên UI
SSH Password admin123a Hardcoded, không hiển thị trên UI
Concurrent devices 10 Số luồng flash song song
Show all Tắt Ẩn gateway và IP máy host
MAX_FLASH_RETRIES 3 Số lần retry nạp FW (tự động)
MAX_SCAN_ROUNDS 15 Số lần scan tối đa (tự động)

🛡️ Xử lý lỗi tổng hợp

Tình huống Hành vi
Scan exception (network error) Log lỗi, chờ 3s, scan lại (đếm vào MAX_SCAN_ROUNDS)
Scan 15 lần chưa đủ thiết bị Hiện popup cảnh báo, dừng tự động
Flash thất bại lần 12 (tự động) Log cảnh báo, chờ 2s, retry tự động
Flash thất bại sau 3 lần retry Log lỗi, đánh dấu , tiếp tục device tiếp theo
User nhấn DỪNG Set stop flag, dừng scan/flash an toàn
IP 192.168.11.102 + New Flash Chặn ngay, hiện cảnh báo
Chưa chọn firmware Hiện popup cảnh báo, không cho bắt đầu
Mạng không hợp lệ Hiện popup cảnh báo, không cho bắt đầu

🔒 Lưu ý bảo mật & Quyền

  • Quyền hệ thống: Quá trình quét của scanner.py chỉ chạy lệnh ping từ hệ điều hành, vì vậy ứng dụng có thể chạy hoàn toàn không cần quyền Admin (trên Windows) / Root (trên macOS/Linux).
  • CREATE_NO_WINDOW: Khi gọi subprocess lệnh ping, ứng dụng dùng flag CREATE_NO_WINDOW trên Windows để ngăn cửa sổ console command prompt (cmd) liên tục hiện lên màn hình.
  • HTTP thuần: Firmware được upload qua HTTP (không HTTPS). Chỉ dùng trong mạng LAN nội bộ, không nên dùng trên mạng mở.
  • Credentials cố định: SSH credentials (root/admin123a) được hardcode trong flash_update_worker.py và truyền từ main.py, không hiển thị trên UI.