Sau 9 bài, chúng ta đã lần lượt xây dựng:
Cơ cấu nhả lon.
Motor và driver.
ESP32.
Encoder/Sensor.
State Machine.
Màn hình cảm ứng.
Thanh toán QR.
Backend Laravel.
Quản lý tồn kho.
Bây giờ chúng ta ghép tất cả lại thành một hệ thống vending machine mini hoàn chỉnh.
Mục tiêu của bài này không phải làm một chiếc máy thương mại ngay lập tức.
Mục tiêu là:
Làm cho một người có thể chọn một lon nước → thanh toán → máy tự động nhả đúng một lon → cảm biến xác nhận → hệ thống cập nhật tồn kho.
1. Kiến trúc tổng thể
Hệ thống cuối cùng:
👤 KHÁCH HÀNG
│
▼
┌───────────────┐
│ TOUCH SCREEN │
└───────┬───────┘
│
Chọn sản phẩm
│
▼
┌───────────────┐
│ ESP32 │
└───────┬───────┘
│
HTTP / HTTPS
│
▼
┌───────────────┐
│ LARAVEL │
│ SERVER │
└───────┬───────┘
│
Tạo Order
│
▼
QR
│
▼
💳 Thanh toán
│
▼
Payment = PAID
│
▼
ESP32
│
▼
┌───────────────┐
│ MOTOR DRIVER │
└───────┬───────┘
│
▼
MOTOR
│
▼
TRỤC XOẮN
│
▼
🥤
│
▼
SENSOR
│
▼
Product Delivered
│
▼
Laravel
│
▼
Stock - 1
Đây chính là kiến trúc IoT vending machine cơ bản.
2. Phần cứng phiên bản mini
Để học, chúng ta không cần làm máy lớn.
Một phiên bản nhỏ có thể gồm:
ESP32
Màn hình cảm ứng
Motor DC hoặc Stepper
Motor Driver
IR Sensor
Encoder/Hall Sensor
Nguồn DC
Nút dừng khẩn cấp
Cơ cấu trục xoắn
Khung máy
Có thể bắt đầu với một ngăn duy nhất.
Ví dụ:
┌──────────────────────────┐
│ │
│ 🥤 🥤 🥤 🥤 │
│ 🥤 🥤 🥤 🥤 │
│ │
│ TRỤC XOẮN │
│ ════════════════════ │
│ │
│ ↓ │
│ SENSOR │
│ ↓ │
│ ┌────────┐ │
│ │ NHẬN │ │
│ │ HÀNG │ │
│ └────────┘ │
└──────────────────────────┘
Sau khi chạy ổn định mới nhân lên:
A1
A2
B1
B2
3. Tách hệ thống thành 4 lớp
Đây là cách chúng ta tránh việc code trở thành một mớ hỗn độn.
Lớp 1 — UI
Touch Screen
Nhiệm vụ:
Hiển thị sản phẩm.
Nhận thao tác.
Hiển thị QR.
Hiển thị trạng thái.
Lớp 2 — Machine Controller
State Machine
Nhiệm vụ:
Quyết định máy đang ở trạng thái nào.
Chuyển trạng thái.
Kiểm soát timeout.
Xử lý lỗi.
Lớp 3 — Hardware
Motor
Sensor
Encoder
Nhiệm vụ:
Chạy motor.
Dừng motor.
Đọc sensor.
Phát hiện sản phẩm.
Lớp 4 — Backend
Laravel
Database
Payment
Inventory
Admin
Nhiệm vụ:
Quản lý sản phẩm.
Quản lý giá.
Tạo Order.
Xác nhận thanh toán.
Quản lý tồn kho.
Ghi log.
Quản lý máy.
4. State Machine hoàn chỉnh
Đây là phần quan trọng nhất.
┌──────────┐
│ IDLE │
└────┬─────┘
↓
┌──────────┐
│ SELECT │
└────┬─────┘
↓
┌───────────────┐
│ CREATE_ORDER │
└───────┬───────┘
↓
┌───────────────┐
│ SHOW_QR │
└───────┬───────┘
↓
┌─────────────────┐
│ WAIT_PAYMENT │
└────────┬────────┘
│
┌───────┴───────┐
↓ ↓
TIMEOUT PAID
↓ ↓
IDLE DISPENSE
↓
CHECK
↙ ↘
SUCCESS ERROR
↓ ↓
IDLE ERROR
Toàn bộ máy sẽ xoay quanh State Machine này.
5. Trạng thái IDLE
Khi không có người sử dụng:
IDLE
Màn hình:
┌──────────────────────────────┐
│ │
│ 🥤 DRINK │
│ │
│ MÁY BÁN NƯỚC │
│ │
│ Chạm để bắt đầu │
│ │
└──────────────────────────────┘
Không có giao dịch nào đang chạy.
6. Trạng thái SELECT
Khách chọn:
A1 — Coca
ESP32 lưu:
selectedSlot = "A1";
Nhưng chưa nhả hàng.
7. Kiểm tra tồn kho
ESP32 hỏi Backend:
GET /api/machines/VM001/products/A1
Server trả:
{
"code": "A1",
"name": "Coca",
"price": 15000,
"stock": 5,
"status": "active"
}
Nếu:
stock > 0
thì tiếp tục.
Nếu:
stock = 0
hiển thị:
HẾT HÀNG
và quay lại:
SELECT
8. Tạo Order
Nếu còn hàng:
A1
Coca
15.000đ
ESP32 gọi:
POST /api/orders
Server tạo:
Order #10001
Trạng thái:
payment_status = pending
delivery_status = pending
9. Hiển thị QR
Server trả về thông tin giao dịch.
ESP32 hiển thị:
┌──────────────────────────────┐
│ COCA │
│ │
│ 15.000đ │
│ │
│ ┌───────────┐ │
│ │ │ │
│ │ QR │ │
│ │ │ │
│ └───────────┘ │
│ │
│ Quét để thanh toán │
│ │
└──────────────────────────────┘
State:
WAIT_PAYMENT
10. Chờ thanh toán
ESP32 định kỳ hỏi:
GET /api/orders/10001
Server trả:
{
"payment_status": "pending"
}
Tiếp tục chờ.
Khi:
{
"payment_status": "paid"
}
ESP32 chuyển:
WAIT_PAYMENT
↓
DISPENSE
11. Timeout
Ví dụ:
120 giây
Nếu khách không thanh toán:
WAIT_PAYMENT
↓
TIMEOUT
↓
IDLE
Màn hình:
Giao dịch đã hết thời gian.
Vui lòng thử lại.
12. Bắt đầu nhả hàng
Khi thanh toán thành công:
DISPENSE
ESP32:
Motor ON
Motor quay:
↻
↻
↻
Trục xoắn đẩy lon về phía trước.
13. Encoder
Nếu dùng encoder, ESP32 có thể biết motor đã quay bao nhiêu.
Ví dụ:
Target = 1000 pulses
Motor chạy:
0
100
200
300
...
900
1000
Khi:
pulse >= target
thì:
motorStop();
14. Nhưng Encoder chưa đủ
Encoder chỉ cho biết:
Motor đã quay đúng vị trí.
Nó không chắc chắn:
Lon đã thực sự rơi xuống hay chưa.
Ví dụ:
Motor
↓
Trục xoắn quay
↓
Lon bị kẹt
Encoder vẫn có thể báo:
OK
Vì vậy cần thêm:
Sensor xác nhận sản phẩm.
15. Sensor xác nhận lon
Ví dụ sử dụng IR Sensor:
🥤
↓
↓
┌─────────┐
│ IR │
│ SENSOR │
└────┬────┘
↓
PRODUCT_DETECTED
ESP32 chờ:
Sensor = DETECTED
16. Hai điều kiện để giao hàng thành công
Chúng ta cần:
Motor position = OK
AND
Product sensor = DETECTED
Khi cả hai đúng:
DELIVERY SUCCESS
17. Nếu sản phẩm không rơi
Ví dụ:
Motor → OK
Sensor → NO PRODUCT
Sau khoảng:
5 giây
máy chuyển:
ERROR
Màn hình:
┌──────────────────────────────┐
│ ! │
│ │
│ KHÔNG THỂ GIAO HÀNG │
│ │
│ Vui lòng liên hệ │
│ nhân viên hỗ trợ. │
└──────────────────────────────┘
18. Không trừ kho khi lỗi
Đây là nguyên tắc rất quan trọng.
Nếu:
Payment = PAID
Delivery = FAILED
thì:
stock không được giảm
Ví dụ:
Stock = 5
Thanh toán
↓
Motor lỗi
↓
Sensor không phát hiện
Stock vẫn = 5
Nhưng Order phải được lưu:
payment_status = paid
delivery_status = failed
Để hệ thống xử lý tiếp.
19. Nếu giao thành công
Nếu:
Sensor = DETECTED
thì:
delivery_status = success
Sau đó:
stock = stock - 1
và ghi:
InventoryLog
Ví dụ:
A1
SALE
-1
Order #10001
20. Cập nhật UI
Màn hình:
┌──────────────────────────────┐
│ │
│ ✓ │
│ │
│ GIAO HÀNG THÀNH CÔNG │
│ │
│ Vui lòng lấy hàng │
│ │
└──────────────────────────────┘
Sau vài giây:
SUCCESS
↓
IDLE
21. Backend Laravel
Kiến trúc API tối thiểu:
/api
│
├── machines
│
├── products
│
├── orders
│
├── payments
│
└── inventory
Ví dụ:
POST /api/orders
GET /api/orders/{id}
GET /api/machines/{code}
GET /api/machines/{code}/products
POST /api/machines/{code}/events
22. API Order
Ví dụ:
public function store(Request $request)
{
$machine = Machine::where(
'code',
$request->machine_code
)->firstOrFail();
$machineProduct = MachineProduct::where(
'machine_id',
$machine->id
)
->where('slot', $request->slot)
->firstOrFail();
if ($machineProduct->stock <= 0) {
return response()->json([
'message' => 'Out of stock'
], 422);
}
$order = Order::create([
'machine_id' => $machine->id,
'machine_product_id' => $machineProduct->id,
'amount' => $machineProduct->product->price,
'payment_status' => 'pending',
'delivery_status' => 'pending',
]);
return response()->json($order);
}
Điểm quan trọng:
Giá lấy từ Database, không lấy từ ESP32.
23. API kiểm tra Order
public function show(Order $order)
{
return response()->json([
'id' => $order->id,
'payment_status' => $order->payment_status,
'delivery_status' => $order->delivery_status,
]);
}
ESP32 chỉ cần biết:
pending
paid
expired
và:
pending
dispensing
success
failed
24. API báo giao hàng
Sau khi cảm biến phát hiện lon:
POST /api/orders/10001/delivery
Dữ liệu:
{
"status": "success"
}
Server xử lý:
Order
↓
delivery_status = success
↓
stock - 1
↓
InventoryLog
Tất cả nên nằm trong một transaction.
25. Một Order hoàn chỉnh
Ví dụ:
Order #10001
Machine:
VM001
Slot:
A1
Product:
Coca
Amount:
15.000đ
Payment:
PAID
Delivery:
SUCCESS
Created:
21:10:01
Paid:
21:10:14
Delivered:
21:10:17
Đây là dữ liệu rất có giá trị cho hệ thống quản lý sau này.
26. ESP32 cần những Module nào?
Không nên viết tất cả trong main.cpp.
Có thể chia:
esp32/
│
├── main.cpp
│
├── config.h
│
├── machine/
│ ├── state_machine.cpp
│ └── state_machine.h
│
├── motor/
│ ├── motor.cpp
│ └── motor.h
│
├── sensor/
│ ├── sensor.cpp
│ └── sensor.h
│
├── display/
│ ├── display.cpp
│ └── display.h
│
├── api/
│ ├── api.cpp
│ └── api.h
│
└── payment/
├── payment.cpp
└── payment.h
Cấu trúc này sẽ giúp dự án dễ mở rộng.
27. main.cpp
Không nên có hàng nghìn dòng code.
Ý tưởng:
void setup()
{
machine.begin();
motor.begin();
sensor.begin();
display.begin();
api.begin();
}
void loop()
{
machine.update();
}
State Machine sẽ điều phối các thành phần còn lại.
28. State Machine
Ví dụ:
enum MachineState
{
IDLE,
SELECT,
CREATE_ORDER,
SHOW_QR,
WAIT_PAYMENT,
DISPENSE,
CHECK,
SUCCESS,
ERROR
};
Biến:
MachineState state = IDLE;
29. Hàm update()
Ý tưởng:
void Machine::update()
{
switch (state)
{
case IDLE:
handleIdle();
break;
case SELECT:
handleSelect();
break;
case CREATE_ORDER:
handleCreateOrder();
break;
case WAIT_PAYMENT:
handlePayment();
break;
case DISPENSE:
handleDispense();
break;
case CHECK:
handleCheck();
break;
case SUCCESS:
handleSuccess();
break;
case ERROR:
handleError();
break;
}
}
Đây là cách tốt hơn rất nhiều so với:
if (...)
{
}
else if (...)
{
}
else if (...)
{
}
else if (...)
{
}
hàng trăm lần trong loop().
30. Không dùng delay() cho toàn bộ máy
Không nên:
motorOn();
delay(5000);
motorOff();
Trong thời gian delay():
UI có thể không phản hồi.
Sensor không được xử lý tốt.
Network bị gián đoạn.
Payment không được kiểm tra.
Thay vào đó sử dụng:
millis()
hoặc một state machine không blocking.
31. Máy cần Internet
ESP32 có thể kết nối:
Wi-Fi
Luồng:
ESP32
↓
Wi-Fi Router
↓
Internet
↓
Laravel Server
Trong phiên bản mini đặt trên bàn học, Wi-Fi là lựa chọn đơn giản nhất.
32. Nếu mất Internet?
Đây là một lỗi phải tính đến.
Ví dụ:
Khách chọn Coca
↓
ESP32 mất Wi-Fi
Không được cho máy hoạt động bình thường như chưa có chuyện gì.
Màn hình:
┌──────────────────────────────┐
│ │
│ ⚠ MẤT KẾT NỐI │
│ │
│ Vui lòng thử lại sau. │
│ │
└──────────────────────────────┘
33. Nếu mất mạng sau khi khách thanh toán?
Tình huống khó hơn:
Khách thanh toán
↓
Payment = PAID
↓
ESP32 mất Wi-Fi
Không được tự động tạo Order mới.
Order cũ vẫn tồn tại trên Server.
Khi ESP32 kết nối lại:
GET Order #10001
Server trả:
PAID
Máy tiếp tục:
DISPENSE
Đây là lý do Order ID phải được lưu trên ESP32 trong quá trình giao dịch.
34. Nút Emergency Stop
Một máy cơ khí thực tế nên có:
Nút dừng khẩn cấp.
Ví dụ:
🔴
EMERGENCY STOP
Khi nhấn:
Motor OFF
ngay lập tức.
Không chờ:
State Machine
xử lý xong.
Đây là một lớp bảo vệ phần cứng riêng.
35. Nguồn điện
Không cấp motor trực tiếp từ chân GPIO ESP32.
Sai:
ESP32 GPIO
│
▼
MOTOR
Đúng:
Power Supply
│
├──────────► Motor Driver ───► Motor
│
└──────────► Voltage Regulator ───► ESP32
ESP32 chỉ gửi:
PWM
DIR
ENABLE
tới driver.
36. GND chung
Nếu dùng nguồn riêng cho motor và ESP32, cần thiết kế mass đúng cách.
Thông thường:
ESP32 GND
│
├──────── Motor Driver GND
│
└──────── Sensor GND
Nhưng nguồn và driver phải được thiết kế phù hợp với dòng tải và nhiễu của motor.
37. Phiên bản prototype nên cực kỳ đơn giản
Đừng bắt đầu bằng:
20 sản phẩm
QR thật
Camera
4G
NFC
Cloud
Dashboard
AI
Chúng ta chỉ cần:
1 sản phẩm
1 motor
1 sensor
1 ESP32
1 màn hình
1 Backend
1 QR demo
Mục tiêu:
Một lon nước được bán thành công từ đầu đến cuối.
Sau khi chạy ổn mới mở rộng.
38. Prototype V1
┌───────────────────────┐
│ │
│ 🥤 COCA │
│ │
│ 15.000đ │
│ │
│ [ MUA NGAY ] │
│ │
└──────────┬────────────┘
│
▼
Laravel
│
▼
QR
│
▼
Thanh toán
│
▼
ESP32
│
▼
Motor
│
▼
🥤
│
▼
Sensor
│
▼
SUCCESS
Nếu V1 chạy được:
Dự án đã thành công về mặt nguyên lý.
39. Prototype V2
Sau đó thêm:
A1
A2
B1
B2
Kiến trúc:
ESP32
│
┌─────────┼─────────┐
↓ ↓ ↓
Motor A Motor B Motor C
│ │ │
Sensor A Sensor B Sensor C
40. Prototype V3
Thêm:
QR thật
Inventory
Admin
Multiple machines
Remote monitoring
Lúc này:
CLOUD
│
┌────────┴────────┐
│ │
VM001 VM002
│ │
ESP32 ESP32
│ │
Vending Vending
41. Dashboard cuối cùng
Laravel Admin có thể hiển thị:
┌──────────────────────────────────────────────┐
│ VENDING DASHBOARD │
├──────────────────────────────────────────────┤
│ │
│ 🟢 VM001 🟢 VM002 🔴 VM003 │
│ Online Online Offline │
│ │
│ Doanh thu hôm nay: 1.250.000đ │
│ Đơn hàng: 87 │
│ Giao thành công: 84 │
│ Lỗi giao hàng: 3 │
│ │
├──────────────────────────────────────────────┤
│ LOW STOCK │
│ │
│ VM001 / B1 / Sprite / Còn 1 │
│ VM002 / A2 / Pepsi / Còn 0 │
└──────────────────────────────────────────────┘
Đây đã không còn đơn thuần là một Arduino project.
Nó trở thành:
Một hệ thống IoT có thiết bị + Backend + Database + Payment + Dashboard.
42. Toàn bộ hệ thống sau 10 bài
👤
│
▼
TOUCH SCREEN
│
▼
ESP32
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Motor Sensor Wi-Fi
│ │ │
▼ ▼ ▼
Cơ khí IR/Encoder INTERNET
│
▼
┌─────────────┐
│ LARAVEL │
└──────┬──────┘
│
┌────────────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
ORDERS PAYMENT INVENTORY
│ │ │
└────────────────────────┼──────────────────┘
│
▼
ADMIN DASHBOARD
43. Thành quả sau Bài 10
Chúng ta đã đi từ:
Một motor
đến:
Một hệ thống vending machine hoàn chỉnh về nguyên lý.
Cụ thể:
Phần cơ khí
Khung
Trục xoắn
Motor
Driver
Cơ cấu nhận hàng
Phần điện tử
ESP32
Sensor
Encoder
Nguồn
Driver
Emergency Stop
Phần phần mềm
State Machine
UI
HTTP
API
Phần Backend
Laravel
Database
Order
Payment
Inventory
Admin
🧪 Bài thực hành cuối của phần cơ bản
Đừng làm ngay một máy 10–20 ngăn.
Hãy làm đúng một ngăn duy nhất.
Phần cứng
[ ] ESP32
[ ] Motor
[ ] Motor Driver
[ ] IR Sensor
[ ] Encoder/Hall Sensor
[ ] Màn hình
[ ] Nút Emergency Stop
[ ] Trục xoắn
Phần mềm
[ ] IDLE
[ ] SELECT
[ ] CREATE_ORDER
[ ] SHOW_QR
[ ] WAIT_PAYMENT
[ ] DISPENSE
[ ] CHECK
[ ] SUCCESS
[ ] ERROR
Backend
[ ] Products
[ ] Machines
[ ] Machine Products
[ ] Orders
[ ] Inventory Logs
[ ] Payment
Luồng kiểm thử
[ ] Chọn Coca
[ ] Kiểm tra tồn kho
[ ] Tạo Order
[ ] Hiển thị QR
[ ] Giả lập PAID
[ ] Motor quay
[ ] Sensor phát hiện lon
[ ] Ghi SUCCESS
[ ] Trừ stock
[ ] Ghi Inventory Log
[ ] Quay về IDLE
Nếu toàn bộ các bước này chạy ổn:
🏆 Bạn đã có một vending machine mini hoạt động đúng nguyên lý.
🎯 Tổng kết 10 bài đầu tiên
Chúng ta đã xây dựng dự án theo đúng hướng của một sản phẩm kỹ thuật:
Bài 1
↓
Ý tưởng vending machine mini
Bài 2
↓
Cấu trúc và nguyên lý cơ khí
Bài 3
↓
Cơ cấu nhả lon
Bài 4
↓
Motor + ESP32
Bài 5
↓
Cảm biến + phản hồi
Bài 6
↓
State Machine
Bài 7
↓
Touch UI
Bài 8
↓
QR Payment
Bài 9
↓
Inventory
Bài 10
↓
GHÉP TOÀN BỘ HỆ THỐNG
Và kiến trúc cuối cùng là:
🥤 VENDING MACHINE
│
┌────────────┼────────────┐
│ │ │
MECHANICAL ELECTRONIC SOFTWARE
│ │ │
Motor ESP32 State Machine
Spiral Sensor Touch UI
Frame Driver API
│ │ │
└────────────┼────────────┘
│
INTERNET
│
▼
LARAVEL
│
┌────────────┼────────────┐
│ │ │
ORDER PAYMENT INVENTORY
│ │ │
└────────────┼────────────┘
│
▼
ADMIN PANEL
Đây là mốc kết thúc phần "làm một vending machine mini chạy được".
Từ đây, phần tiếp theo có thể chuyển sang thiết kế cơ khí thực tế, PCB, nguồn, hộp máy, nhiều ngăn, làm lạnh, chống kẹt lon, giám sát từ xa và đưa máy từ prototype thành sản phẩm có thể chế tạo.




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