Ở bài 6, chúng ta đã xây dựng State Machine cho bộ não của máy.
Bây giờ đến phần mà khách hàng nhìn thấy đầu tiên:
Giao diện người dùng (UI/UX).
Một máy bán nước có phần cơ khí rất tốt nhưng giao diện khó sử dụng thì trải nghiệm vẫn rất tệ.
Mục tiêu của chúng ta là tạo một giao diện hiện đại, đơn giản, dễ bấm, lấy cảm hứng từ phong cách giao diện Windows/Material nhưng không sao chép logo hay nhận diện thương hiệu.
1. Màn hình của máy
Phiên bản mini có thể bắt đầu với:
Màn hình cảm ứng 3.5–4.3 inch.
ESP32.
Độ phân giải khoảng 480×272 hoặc 800×480.
Giao tiếp SPI hoặc RGB tùy loại màn hình.
Nếu muốn làm giao diện đẹp hơn, có thể chọn:
ESP32 + màn hình cảm ứng 4.3 inch + LVGL
LVGL là thư viện giao diện rất phù hợp cho các thiết bị nhúng.
2. Không thiết kế quá nhiều nút
Một lỗi phổ biến khi thiết kế máy bán hàng là đưa quá nhiều thông tin lên màn hình.
Ví dụ không nên:
┌───────────────────────────────┐
│ A1 Coca 10.000 │ A2 Pepsi... │
│ B1 Sprite ... │ B2 Fanta... │
│ C1 Water ... │ C2 Tea ... │
│ │
│ QR │ CASH │ CARD │ SETTINGS │
└───────────────────────────────┘
Khách nhìn vào sẽ mất thời gian.
Chúng ta nên thiết kế:
┌───────────────────────────────┐
│ CHỌN ĐỒ UỐNG │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 🥤 │ │ 🥤 │ │
│ │ Coca │ │ Pepsi │ │
│ │ 15.000đ │ │ 15.000đ │ │
│ └─────────┘ └─────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 🥤 │ │ 🥤 │ │
│ │ Sprite │ │ Fanta │ │
│ │ 15.000đ │ │ 15.000đ │ │
│ └─────────┘ └─────────┘ │
└───────────────────────────────┘
Khách chỉ cần:
Nhìn → chọn → thanh toán → nhận hàng.
3. Thiết kế theo State Machine
Điểm quan trọng là:
UI không được tách rời State Machine.
Mỗi trạng thái của máy tương ứng với một màn hình.
IDLE
↓
Màn hình chờ
SELECT
↓
Màn hình chọn sản phẩm
PAYMENT
↓
Màn hình thanh toán
DISPENSE
↓
Màn hình đang lấy hàng
CHECK
↓
Màn hình kiểm tra
SUCCESS
↓
Màn hình hoàn tất
ERROR
↓
Màn hình báo lỗi
Như vậy phần mềm rất dễ quản lý.
4. Màn hình chờ — IDLE
Khi không có người sử dụng:
┌──────────────────────────────┐
│ │
│ VENDING │
│ │
│ 🥤 DRINK BAR │
│ │
│ Chạm để chọn sản phẩm │
│ │
│ 10:30 │
└──────────────────────────────┘
Có thể thêm:
Logo máy.
Hình ảnh sản phẩm.
Nhiệt độ.
Khuyến mãi.
Wi-Fi/4G.
Đồng hồ.
Nhưng không nên nhồi quá nhiều thông tin.
5. Màn hình chọn sản phẩm
Đây là màn hình quan trọng nhất.
Ví dụ:
┌──────────────────────────────┐
│ Chọn sản phẩm │
│ │
│ ┌────────┐ ┌────────┐ │
│ │ Coca │ │ Pepsi │ │
│ │ 15.000 │ │ 15.000 │ │
│ │ A1 │ │ A2 │ │
│ └────────┘ └────────┘ │
│ │
│ ┌────────┐ ┌────────┐ │
│ │ Sprite │ │ Fanta │ │
│ │ 15.000 │ │ 15.000 │ │
│ │ B1 │ │ B2 │ │
│ └────────┘ └────────┘ │
└──────────────────────────────┘
Mỗi sản phẩm nên hiển thị:
Hình ảnh.
Tên.
Giá.
Mã sản phẩm.
Trạng thái còn hàng.
6. Hết hàng
Không nên để khách bấm vào sản phẩm rồi mới báo hết hàng.
Ngay trên UI:
┌───────────────┐
│ │
│ Coca │
│ │
│ HẾT HÀNG │
│ │
└───────────────┘
Nút phải chuyển sang trạng thái:
DISABLED
Không thể nhấn.
7. Chọn sản phẩm
Khi khách chọn Coca:
┌──────────────────────────────┐
│ COCA │
│ │
│ 🥤 │
│ │
│ 15.000đ │
│ │
│ [ MUA NGAY ] │
│ │
│ [ QUAY LẠI ] │
└──────────────────────────────┘
Không nên bắt khách đi qua quá nhiều màn hình.
Mục tiêu:
Từ màn hình chính đến thanh toán trong 1–2 thao tác.
8. Màn hình thanh toán
Ví dụ:
┌──────────────────────────────┐
│ THANH TOÁN │
│ │
│ Coca │
│ 15.000đ │
│ │
│ ┌───────────────┐ │
│ │ QR │ │
│ │ ██████ │ │
│ │ ██ ██ │ │
│ └───────────────┘ │
│ │
│ Quét mã để thanh toán │
│ │
│ [ HỦY GIAO DỊCH ] │
└──────────────────────────────┘
Sau này có thể thêm:
QR
NFC
Thẻ
Tiền mặt
Nhưng bản mini đầu tiên chỉ cần QR là đủ.
9. Thanh toán thành công
Không chuyển ngay sang màn hình khác.
Nên cho khách thấy:
┌──────────────────────────────┐
│ │
│ ✓ │
│ │
│ THANH TOÁN THÀNH CÔNG │
│ │
│ 15.000đ │
│ │
└──────────────────────────────┘
Sau khoảng thời gian rất ngắn:
PAYMENT
↓
DISPENSE
10. Màn hình đang nhả hàng
Đây là phần rất quan trọng.
Không nên để màn hình đứng yên.
┌──────────────────────────────┐
│ │
│ ĐANG LẤY HÀNG │
│ │
│ ↻ │
│ │
│ Vui lòng chờ... │
│ │
└──────────────────────────────┘
Có thể hiển thị animation:
Motor
↻
↻
↻
Trong lúc đó ESP32 thực hiện:
Motor ON
↓
Encoder đếm
↓
Đủ vị trí
↓
Motor OFF
11. Cảm biến xác nhận
Sau khi motor dừng:
DISPENSE
↓
CHECK
↓
IR Sensor
Nếu cảm biến phát hiện lon:
SUCCESS
Nếu không:
ERROR
UI lúc này có thể hiển thị:
✓ Đã giao sản phẩm
Vui lòng lấy hàng.
12. Màn hình lỗi
Không nên sử dụng những thông báo kỹ thuật như:
ERROR CODE: MOTOR_04
Khách hàng không hiểu.
Thay bằng:
┌──────────────────────────────┐
│ ! │
│ │
│ KHÔNG THỂ LẤY HÀNG │
│ │
│ Vui lòng thử lại hoặc liên │
│ hệ nhân viên hỗ trợ. │
│ │
│ [ TRỞ VỀ ] │
└──────────────────────────────┘
Mã lỗi kỹ thuật chỉ dành cho màn hình quản trị.
13. Màu sắc
Với concept máy mà chúng ta đang thiết kế, có thể sử dụng:
Background
#F5F7FA
Primary
#2563EB
Success
#16A34A
Warning
#F59E0B
Danger
#DC2626
Text
#111827
Không cần sử dụng quá nhiều màu.
Quy tắc đơn giản:
1 màu chính + 1 màu cảnh báo + 1 màu thành công.
14. Thiết kế nút bấm
Nút cảm ứng phải đủ lớn.
Không nên:
[ OK ]
quá nhỏ.
Nên:
┌──────────────────────┐
│ MUA NGAY │
└──────────────────────┘
Khách đứng trước máy có thể:
tay ướt
ánh sáng mạnh
thao tác nhanh
Vì vậy vùng cảm ứng phải đủ rộng.
15. Không nên dùng quá nhiều animation
Animation chỉ nên giúp khách hiểu:
Đang xử lý
Đang thanh toán
Đang nhả hàng
Hoàn tất
Không nên:
hiệu ứng bay quá nhiều
chuyển cảnh quá lâu
animation nặng
video nền
Máy bán hàng cần:
Nhanh hơn là đẹp.
16. Kiến trúc UI
Chúng ta có thể tổ chức phần mềm:
UI
│
├── HomeScreen
├── ProductScreen
├── PaymentScreen
├── DispenseScreen
├── SuccessScreen
└── ErrorScreen
Trong khi phần điều khiển:
Core
│
├── VendingMachine
├── MotorController
├── SensorController
├── PaymentController
└── Inventory
Hai phần này giao tiếp với nhau.
UI
│
▼
State Machine
│
┌────────┼────────┐
▼ ▼ ▼
Motor Sensor Payment
Đây là cách tổ chức tốt hơn rất nhiều so với việc nhét toàn bộ chương trình vào loop().
17. Dùng LVGL
Ở phiên bản đầu tiên, chúng ta có thể dùng LVGL để xây dựng UI.
Kiến trúc:
ESP32
│
├── LVGL
│ │
│ ├── Button
│ ├── Label
│ ├── Image
│ ├── Screen
│ └── Animation
│
├── Touch Driver
│
└── Vending Controller
LVGL xử lý giao diện.
ESP32 xử lý logic máy.
18. Ví dụ tạo nút
Một ví dụ đơn giản với LVGL:
lv_obj_t *btn;
btn = lv_button_create(lv_screen_active());
lv_obj_set_size(btn, 200, 70);
lv_obj_center(btn);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "MUA NGAY");
lv_obj_center(label);
Khi khách chạm vào nút, chương trình có thể chuyển State Machine:
currentState = SELECT;
Hoặc nếu đang ở màn hình chọn sản phẩm:
selectedProduct = 0;
currentState = PAYMENT;
19. UI không được điều khiển trực tiếp Motor
Đây là nguyên tắc rất quan trọng.
Không nên:
Button
↓
Motor
Mà phải:
Button
↓
UI Event
↓
State Machine
↓
Motor Controller
↓
Motor
Như vậy sau này:
màn hình bị thay đổi
thêm nút vật lý
thêm app điện thoại
thêm Web Admin
thì Motor Controller vẫn giữ nguyên.
20. Thiết kế hoàn chỉnh
Đến đây, hệ thống của chúng ta đã có kiến trúc:
TOUCH SCREEN
│
▼
UI/LVGL
│
▼
STATE MACHINE
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
PAYMENT MOTOR SENSOR
│ │ │
▼ ▼ ▼
QR/API DRIVER IR/ENCODER
│
▼
MOTOR
│
▼
SPIRAL
│
▼
🥤
Đây chính là lúc dự án bắt đầu giống một sản phẩm thực tế, thay vì chỉ là một mạch ESP32 điều khiển motor.
🧪 Bài thực hành
Trong bài này chưa cần làm toàn bộ giao diện.
Hãy làm theo thứ tự:
Bước 1
Tạo màn hình:
CHỌN SẢN PHẨM
Bước 2
Tạo 4 nút:
A1 A2
B1 B2
Bước 3
Khi nhấn A1:
selectedProduct = A1;
Bước 4
Chuyển sang:
PAYMENT
Bước 5
Giả lập thanh toán thành công bằng một nút:
THANH TOÁN
Bước 6
Chuyển:
PAYMENT
↓
DISPENSE
Sau đó mới kết nối với motor thật.
🎯 Tổng kết
Sau bài 7, chúng ta đã thiết kế được giao diện người dùng hoàn chỉnh về mặt luồng:
┌──────────┐
│ IDLE │
└────┬─────┘
↓
┌──────────┐
│ SELECT │
└────┬─────┘
↓
┌──────────┐
│ PAYMENT │
└────┬─────┘
↓
┌──────────┐
│ DISPENSE │
└────┬─────┘
↓
┌──────────┐
│ CHECK │
└────┬─────┘
↓
┌──────────┐
│ SUCCESS │
└────┬─────┘
↓
IDLE
Quan trọng nhất là chúng ta đã thống nhất một nguyên tắc:
UI chỉ nhận thao tác của người dùng. State Machine quyết định máy phải làm gì. Controller điều khiển phần cứng.
Đây sẽ là nền tảng để các bài sau không biến thành một đống code Arduino khó bảo trì.
Bài 8 — Kết nối thanh toán QR
Ở bài tiếp theo, chúng ta sẽ đưa thanh toán thật vào máy: tạo mã QR theo sản phẩm, nhận trạng thái thanh toán qua Internet, xác nhận giao dịch rồi mới cho phép ESP32 kích hoạt cơ cấu nhả lon.




Không có nhận xét nào:
Đăng nhận xét