-
-
-
Tổng tiền thanh toán:
-
Tủ bán hàng có đèn báo từng vị trí
24/08/2026
Tủ bán hàng có đèn báo từng vị trí
1. Cấu trúc hệ thống
PC / điện thoại
│
▼
ESP32 TỦ 1 — WEB CHÍNH 192.168.0.50
│
├──── TỦ 1: vị trí 1–256
│
└──── ESP32 TỦ 2 192.168.0.51 vị trí 1–256
Mỗi sản phẩm sẽ có thêm thông tin Tủ, và khi bấm CHỌN / LẤY HÀNG, ESP32 Web chính sẽ gửi đúng lệnh đến ESP32 của tủ tương ứng.
Đặc biệt, ta sẽ xử lý luôn tồn kho dùng chung, tránh trường hợp hai tủ cùng sửa products.json rồi dữ liệu bị lệch.
- Đặt IP cố định cho Tủ 1 và Tủ 2.
- Tạo ESP32 Tủ 1 làm Web chính.
- Kết nối Tủ 2 với Tủ 1 qua WiFi/LAN.
- Thêm trường Tủ vào
products.json. - Sửa CHỌN → LẤY HÀNG để gửi đúng lệnh đến từng ESP32.
- Đồng bộ tồn kho.
- Cuối cùng kiểm tra trường hợp 2 sản phẩm trùng vị trí và 2 tủ cùng có sản phẩm giống nhau.
ROUTER WIFI 192.168.1.1
│
┌─────────┴─────────┐
│ │
ESP32 TỦ 1 ESP32 TỦ 2
192.168.1.50 192.168.1.51
│ │
MAX7219 MAX7219
TFT TFT
Keypad Keypad
Servo Servo
Tủ 1 = Web chính
Bạn truy cập: http://192.168.1.50
và không cần truy cập 192.168.1.51.
Tủ 2 vẫn chạy ESP32 độc lập để điều khiển phần cứng.
2. Quản lý kho
PC
│
▼
┌──────────────────┐
│ ESP32 TỦ 1 │
│ WEB MASTER │
│ 192.168.1.50 │
│ │
│ products.json │
└────────┬─────────┘
│ WiFi/LAN
│
┌────────▼─────────┐
│ ESP32 TỦ 2 │
│ SLAVE │
│ 192.168.1.51 │
│ │
│ chỉ nhận lệnh │
│ điều khiển máy │
└──────────────────┘
Tủ 1 quản lý dữ liệu sản phẩm, Tủ 2 nhận lệnh điều khiển.
Như vậy:
- Upload CSV → chỉ cần upload ở Tủ 1.
- Edit sản phẩm → Tủ 1.
- Download CSV → Tủ 1.
- Tìm kiếm → Tủ 1.
- Web → Tủ 1.
- Chọn sản phẩm thuộc Tủ 2 → Tủ 1 gửi lệnh cho Tủ 2.
- Tủ 2 bật LED/TFT/servo.
- Tồn kho → Tủ 1 trừ và lưu
products.json.
3. Tạo MASTER và SLAVE
- Tủ 1 = MASTER
- Tủ 2 = SLAVE
- Giữ nguyên toàn bộ giao diện web hiện tại.
- Hai ESP32 vẫn chạy giao diện riêng như hiện tại.
- MASTER có thể gửi lệnh cho SLAVE.
- SLAVE nhận lệnh và phản hồi.
- Kiểm tra bằng PING → PONG trước.
- Khi PING/PONG ổn định mới chuyển bước 2: thêm
cabinetvàoproducts.json.
Kiến trúc trước mắt
WiFi Router
│
┌──────────┴──────────┐
│ │
ESP32 TỦ 1 ESP32 TỦ 2
MASTER SLAVE
│ │
│────── PING ────────>│
│<──── PONG ──────────│
│ │
│──── COMMAND ───────>│
│<──── RESPONSE ──────│
4. Hoạt động tìm kiếm:
Bàn phím / Web nhập mã
│
▼
MASTER tìm products.json của Tủ 1
│
├── tìm thấy → xử lý Tủ 1
│
└── không thấy
│
▼
gửi SEARCH sang SLAVE
│
▼
SLAVE trả FOUND
│
▼
MASTER hiển thị sản phẩm
Điều kiện thuật toán
SEARCH
│
▼
┌───────────────┐
│ TỦ 1 / MASTER │
└───────┬───────┘
│
tìm thấy?
/ \
YES NO
│ │
▼ ▼
dùng Tủ 1 hỏi Tủ 2
│
FOUND?
/ \
YES NO
│ │
▼ ▼
dùng Tủ 2 Không tìm
5. Tìm kiếm bằng bàn phím
BÀN PHÍM
↓
nhập mã SP
↓
searchProductAllCabinets(input)
↓
┌───────────────────────┐
│ Tìm products.json │
│ MASTER │
└───────────┬───────────┘
│ không có
↓
PING SLAVE
↓
PONG
↓
/cabinet/search
↓
FOUND|1|2212001|...
↓
cập nhật currentCode
currentPosition
currentName
currentStock
currentPrice
↓
TFT / màn hình
Web
Hai phần như này
WEB /search
│
▼
searchProductAllCabinets()
│
├── Tìm MASTER
│ └── thấy → hiển thị
│
└── không thấy
│
├── PING SLAVE
│
└── SEARCH SLAVE
└── thấy → hiển thị
Phím D
│
▼
searchProductAllCabinets()
Kiến trúc cố định
BÀN PHÍM MASTER ──┐
│
WEB MASTER ───────┼──> searchProductAllCabinets(code)
│
↓
TÌM products.json MASTER
│
┌───────┴───────┐
│ │
FOUND NOT FOUND
│ │
↓ ↓
xử lý SP PING SLAVE
│
PONG
↓
searchProductOnSlave()
│
FOUND
↓
xử lý SP SLAVE
Luồng đúng mà chúng ta đã chốt phải là:
Bấm D / Web Search
↓
handleSearch()
↓
searchProductAllCabinets(code)
↓
Tìm products.json MASTER
↓
Không có
↓
PING SLAVE 1 lần
↓
PONG
↓
searchProductOnSlave(code)
↓
FOUND|...
↓
Cập nhật currentCode
currentPosition
currentName
currentStock
currentPrice
↓
LED + TFT + Web + Servo
Cấu trúc mới chốt là:
Tủ / VT / Tên sản phẩm / Số lượng / Giá
{ "cabinet": 1, "position": 1, "code": "2404066", "name": "MEGA2560 PRO mini CH340", "stock": 1, "price": 310001 },
Khi Master tìm 2212001, kết quả Slave hiện là:
FOUND|2|1|2212001|STM32 STM 32 F103 C8T6 Arm|10|95000
products.json- Đọc/tìm sản phẩm Master
- Tìm sản phẩm Slave
- Giao thức
FOUND|...có thêmcabinet -
Hiển thị Web:
Tủ | VT | Tên sản phẩm | Số lượng | Giá - Keypad Master → tìm Master/Slave
- Web Master → tìm Master/Slave
- Bật LED đúng tủ + vị trí
- Giữ nguyên các phần hiện đang hoạt động.
Tìm kiếm có 2 kiểu
Tìm mã SP
↓
searchProductFromFile()
↓
MASTER tìm trước
↓
không thấy
↓
searchProductOnSlave()
↓
tìm SLAVE
Tìm vị trí
↓
handleSelect()
TÌM MÃ SP
handleSelect()
→ currentCabinet = 1
searchProductAllCabinets()
├── tìm MASTER → currentCabinet = 1
└── tìm SLAVE → searchProductOnSlave()
→ currentCabinet = 2
Tìm sp tủ 2 từ master thì cũng trừ số lượng
MASTER tìm SP
↓
SLAVE nhận DISPLAY
↓
LCD + LED
↓
currentCode/currentPosition/currentStock được lưu
↓
A có sản phẩm để trừ
5. Cấu hình thiết bị
Với code hiện tại của bạn, giới hạn không nằm ở LittleFS trước mà nằm ở RAM khi deserializeJson() đọc toàn bộ products.json vào bộ nhớ.
Một sản phẩm kiểu:
{
"cabinet": 2,
"position": 1,
"code": "2212001",
"name": "STM32 STM 32 F103 C8T6 Arm",
"price": 95000,
"stock": 10
}
Thường chiếm khoảng 100–160 byte trong file JSON, nhưng khi ArduinoJson parse vào RAM thì cần nhiều hơn đáng kể.
Với ESP32 của bạn đang chạy cùng lúc Wi-Fi, WebServer, TFT, MAX7219, Keypad, Servo, HTTPClient và ArduinoJson, tôi khuyên mức thực tế như sau:
| Số SP / ESP32 | Đánh giá |
|---|---|
| 200–300 | Rất an toàn |
| 500 | An toàn |
| 700–800 | Khá ổn |
| 1.000 | Có thể chạy nhưng cần theo dõi RAM |
| 1.500+ | Không khuyến nghị với cách đọc JSON hiện tại |
| 2.000+ | Dễ thiếu heap/reset/lỗi deserialize |
Vì bạn đang dùng 2 tủ, tôi sẽ thiết kế khoảng:
Tủ 1: tối đa khoảng 700–800 SP
Tủ 2: tối đa khoảng 700–800 SP
--------------------------------
Tổng: khoảng 1.400–1.600 SP
mà vẫn có biên an toàn.
Nếu mục tiêu sau này lên 3.000–10.000 sản phẩm, không nên tiếp tục mỗi lần tìm lại:
deserializeJson(doc, file);
JsonArray arr = doc.as<JsonArray>();
toàn bộ file. Lúc đó nên đổi cấu trúc lưu sang nhiều file nhỏ / file index / database dạng key-value, để tìm một sản phẩm mà không phải nạp toàn bộ danh sách vào RAM.
Với hệ thống tủ của bạn hiện tại, tôi nghiêng về đặt giới hạn thiết kế 500–700 sản phẩm mỗi ESP32 để chạy ổn định lâu dài.
mở rộng thành nhiều tủ mà MASTER tìm tuần tự từng ESP32 một, thời gian tìm sẽ tăng theo số tủ.
Ví dụ nếu mỗi lần hỏi một SLAVE mất khoảng 100–300 ms:
5 tủ → khoảng 0.5–1.5 giây
10 tủ → khoảng 1–3 giây
20 tủ → khoảng 2–6 giây
Nếu có tủ mất kết nối và timeout 1–2 giây thì còn chậm hơn nhiều.
Với hệ thống nhiều tủ, nên đổi kiến trúc theo hướng này: mỗi sản phẩm có trường cabinet, và MASTER có một bảng chỉ mục mã SP → số tủ. Khi tìm mã, MASTER biết ngay phải hỏi ESP32 nào, thay vì ping lần lượt tất cả tủ. Khi đó dù 5 hay 50 tủ, thời gian tìm gần như vẫn chỉ là một lần HTTP tới đúng tủ, thường vài chục đến vài trăm ms.
Ví dụ MASTER giữ index nhỏ:
{
"2212001": 2,
"1701028": 1,
"2304016": 5
}
Khi tìm 2304016:
MASTER tra index
→ biết Tủ 5
→ gọi trực tiếp 192.168.1.55
→ Tủ 5 trả sản phẩm
Cách này nhanh hơn rất nhiều.
Với hệ thống của bạn, tôi khuyên nếu mở rộng trên 5–10 tủ, nên chuyển sang kiến trúc:
MASTER
├─ lưu index toàn hệ thống
├─ biết IP từng tủ
└─ gửi lệnh trực tiếp đúng tủ
SLAVE 1 → products riêng
SLAVE 2 → products riêng
SLAVE 3 → products riêng
...
Như vậy 20–30 tủ vẫn tìm nhanh, không phải quét tuần tự từng ESP32.