Hướng dẫn viết API nhận kết quả đọc biển số từ camera TingPush
Tài liệu cho lập trình viên xây dựng máy chủ nhận dữ liệu biển số từ camera TingPush (RV1126BP-LPR).
Mỗi khi camera đọc chắc chắn một biển số, nó gửi thẳng một yêu cầu HTTP POST (JSON) tới URL của bạn.
Bạn chỉ cần viết một endpoint nhận yêu cầu đó và trả 200.
Không muốn tự viết API? Để camera gửi về máy chủ TingPush (mặc định) rồi kéo dữ liệu qua API công khai / xuất file: Hướng dẫn API công khai TingPush — lấy dữ liệu biển số đã lưu. Tài liệu này dành cho trường hợp chọn API riêng.
Mã mẫu chạy được ngay: vi_du_api/ — C# (ASP.NET Core), Node.js (Express), PHP, Python (FastAPI).
Công cụ tự kiểm tra API: vi_du_api/kiem_tra_api.py.
1. Tổng quan
Camera ──(đọc biển, đủ số lần xác nhận)──► POST https://may-chu-cua-ban/api/plates (JSON, có thể kèm ảnh)
◄──────────────── 200 OK ──────────
- Mỗi camera một URL riêng. Chủ camera khai báo trên app TingPush Camera → Cài đặt camera → Gửi dữ liệu
biển số → Nơi nhận dữ liệu = API riêng (hoặc trên trang cấu hình web của camera, tab Biển số & API). Nhiều camera có thể dùng chung một URL —
phân biệt bằng
camera_id. - Camera gọi trực tiếp tới URL của bạn (không qua máy chủ TingPush). URL có thể là địa chỉ Internet
(
https://api.congty.vn/...) hoặc địa chỉ trong mạng nội bộ của bãi xe (http://192.168.1.10:5000/...) — miễn là camera kết nối tới được. - Mỗi biển số chỉ gửi một lần mỗi lượt xe (camera tự chống gửi lặp khi xe đứng lâu trước camera, mặc định 60 giây).
- Camera chỉ đọc biển trong giờ đọc và vùng đọc chủ camera đặt (app → Cài đặt đọc biển số).
- Camera có hàng đợi lưu trên bộ nhớ: máy chủ của bạn tắt hay mất mạng thì camera giữ lại (tối đa 300 bản ghi) và tự gửi lại khi kết nối lại, kể cả sau khi camera mất điện.
2. Yêu cầu camera gửi
POST /api/plates HTTP/1.1
Host: may-chu-cua-ban
User-Agent: lpr-camera/1.0
Content-Type: application/json; charset=utf-8
Content-Length: 1234
Connection: close
Authorization: Bearer abc123 ← chỉ có khi chủ camera khai báo (tên header đổi được, VD X-Api-Key)
{ ...JSON bên dưới... }
- Phương thức luôn là
POST, nội dung luôn là một JSON object, mã hóa UTF-8. - Camera không theo chuyển hướng (301/302/307/308) mà coi là lỗi tạm thời và gửi lại mãi — khai báo đúng URL cuối
cùng (đúng
http/https, có/khôngwww, có/không/ở cuối). - HTTPS: được hỗ trợ. Mặc định camera không kiểm tra chứng chỉ (chứng chỉ tự ký vẫn dùng được); chủ camera có thể bật Kiểm tra chứng chỉ HTTPS — khi đó cần chứng chỉ hợp lệ (VD Let's Encrypt).
Ví dụ JSON
{
"event_id": "LPR-9MHK902C8J5Z-1792114200-000042",
"type": "plate",
"device_id": "CONG-VAO-1",
"camera_id": "LPR-9MHK902C8J5Z",
"plate": "59T184807",
"plate_display": "59T1-848.07",
"raw_text": "59T1-84807",
"confidence": 0.912,
"read_count": 3,
"time": "2026-10-16T08:30:00+07:00",
"timestamp": 1792114200,
"box": [1520, 980, 1810, 1105],
"frame_width": 3840,
"frame_height": 2160,
"image_type": "jpeg",
"image": "/9j/4AAQSkZJRgABAQ...",
"plate_image_type": "jpeg",
"plate_image": "/9j/4AAQSkZJRgABAQ..."
}
Bản không ảnh: vi_du_api/sample_request.json.
Ý nghĩa các trường
| Trường | Kiểu | Luôn có | Ý nghĩa |
|---|---|---|---|
event_id |
string | ✔ | Mã duy nhất của lần đọc. Camera gửi lại (do mất trả lời) thì event_id giữ nguyên → dùng để bỏ bản trùng (mục 6). Dạng <camera_id>-<timestamp>-<số thứ tự>, tối đa 100 ký tự — chỉ coi là chuỗi, đừng tách. |
type |
string | ✔ | "plate" (biển số) hoặc "attendance" (chấm công khuôn mặt — camera bật chấm công gửi tới cùng URL, định dạng xem Chấm công bằng khuôn mặt — camera TingPush làm máy chấm công mục 5). Sự kiện AI khác (xâm nhập, khuôn mặt, đếm) không gửi tới API riêng — chúng đi về máy chủ TingPush để cảnh báo; lấy qua /api/v1/events (Sự kiện AI & cảnh báo — đếm người / xe, xâm nhập vùng cấm, nhận diện khuôn mặt). API chỉ xử lý biển số: trả 200 và bỏ qua bản ghi có type khác plate. |
camera_id |
string | ✔ | ID vĩnh viễn của camera (LPR- + 12 ký tự), không đổi suốt đời camera, kể cả khi cài lại phần mềm. Dùng làm khóa nhận diện camera. |
device_id |
string | ✔ | Mã do người lắp đặt tự đặt trên trang cấu hình (VD CONG-VAO-1), mặc định theo địa chỉ MAC. Tiện để hiển thị / phân làn; có thể bị đổi. |
plate |
string | ✔ | Biển số đã chuẩn hóa: chữ in hoa + số, không dấu gạch / chấm / khoảng trắng (VD 30A12345, 59T184807). Dùng để lưu, tìm kiếm, so khớp. |
plate_display |
string | ✔ | Biển số để hiển thị đúng kiểu Việt Nam (VD 30A-123.45, 59T1-848.07). |
raw_text |
string | ✔ | Chuỗi model đọc được (biển 2 dòng nối bằng -). Để tra cứu khi đọc sai. |
confidence |
number | ✔ | Độ tin cậy khung biển tốt nhất, 0–1. |
read_count |
int | ✔ | Số lần camera đọc ra cùng một biển trước khi xác nhận (≥ ngưỡng cài đặt, mặc định 3). |
time |
string | ✔ | Thời điểm xác nhận, ISO 8601 có múi giờ của camera (VD +07:00). |
timestamp |
int | ✔ | Cùng thời điểm, giây UNIX (UTC). Nên dùng trường này để lưu / so sánh. |
box |
int[4] | ✔ | Vị trí biển trên khung hình [x1, y1, x2, y2] (điểm ảnh). |
frame_width, frame_height |
int | ✔ | Kích thước khung hình mà box tham chiếu (thường 3840×2160). |
image |
string | khi bật | Ảnh toàn cảnh JPEG, base64 (không có tiền tố data:). Ảnh nhỏ (luồng phụ) ~50–150 KB, ảnh nét (luồng chính 4K) ~0,5–2 MB. |
image_type |
string | khi có image |
Luôn "jpeg". |
plate_image |
string | khi bật | Ảnh cắt vùng biển số JPEG màu, base64, ~5–20 KB, cắt từ khung gốc độ phân giải cao (chừa lề quanh biển). |
plate_image_type |
string | khi có plate_image |
Luôn "jpeg". |
allowed |
bool | khi camera có danh sách xe | true = biển có trong danh sách xe cho vào (còn hạn), false = không có. Không có trường này khi camera chưa được gán danh sách — xem Thiết bị ngoại vi — mở barie, cửa cuốn, còi, đèn từ camera TingPush. |
allow_name |
string | khi allowed = true |
Tên chủ xe / ghi chú trong danh sách (hoặc tên danh sách). |
test |
bool | chỉ bản thử | true khi người dùng bấm Gửi thử trên app / trang cấu hình (biển mẫu 30A-123.45). Trả 200 nhưng không lưu. |
Lưu ý:
- Bỏ qua các trường không biết. Phiên bản phần mềm camera sau có thể thêm trường mới; đừng từ chối yêu cầu vì có trường lạ.
image/plate_imagecó hay không tùy chủ camera tích Gửi ảnh toàn cảnh / Gửi ảnh biển số — API phải chạy được cả khi không có ảnh.- Giờ trên camera có thể lệch nếu camera chưa đồng bộ giờ (app có nút Đồng bộ giờ). Nên lưu thêm giờ máy chủ nhận
được (
received_at) bên cạnhtimestamp.
3. Trả lời camera — quan trọng nhất
Camera chỉ đọc mã trạng thái HTTP, không đọc nội dung trả về (nội dung chỉ hiện ra khi người dùng bấm Gửi thử).
| Bạn trả | Camera hiểu | Camera làm |
|---|---|---|
| 2xx (200, 201, 204) | Đã nhận | Xóa bản ghi khỏi hàng đợi, gửi bản tiếp theo |
| 4xx (400, 401, 403, 404, 413, 422…) | Bản ghi bị từ chối hẳn | BỎ bản ghi, không gửi lại |
| 408, 429 | Tạm thời | Giữ lại, gửi lại sau |
| 5xx, 3xx (chuyển hướng), quá thời gian chờ, mất kết nối, lỗi DNS / TLS | Tạm thời | Giữ lại, gửi lại sau 5 → 10 → 20 → 40 → 60 giây (tối đa 60 giây/lần) |
Hệ quả:
- Đừng trả 4xx cho lỗi tạm thời (CSDL đang bận, dịch vụ khác chưa sẵn sàng…) — trả
503, camera sẽ gửi lại. Trả4xxthì bản ghi mất luôn. - Sai token →
401là đúng (camera bỏ, không gửi lại vô ích) — nhưng nhớ: các xe đi qua trong lúc token sai sẽ không được gửi lại sau khi sửa token. - Trả lời nhanh: camera chờ tối đa Thời gian chờ máy nhận (mặc định 8 giây, đặt được 5–60 giây). Quá thời
gian, camera coi là lỗi và gửi lại → bạn nhận bản trùng. Hãy lưu bản ghi rồi trả
200ngay; việc chậm (gọi hệ thống khác, mở barie, gửi thông báo…) đưa vào hàng đợi / tác vụ nền. - Camera gửi lần lượt từng bản ghi, theo thứ tự cũ → mới. Một bản ghi bị lỗi tạm thời sẽ chặn các bản sau cho tới khi gửi được — nên API cần ổn định, đừng trả 5xx lâu dài cho một bản ghi cụ thể.
- Nội dung trả về nên ngắn (VD
{"ok":true}), camera chỉ đọc tối đa 2 KB.
4. Dung lượng yêu cầu
Ảnh toàn cảnh luồng chính 4K có thể tới 2 MB → ~3 MB sau base64. Mặc định của một số máy chủ quá nhỏ (gặp lỗi
413 → camera bỏ bản ghi). Đặt giới hạn ≥ 20 MB:
| Nền tảng | Cấu hình |
|---|---|
| nginx (proxy phía trước) | client_max_body_size 20m; (mặc định chỉ 1 MB!) |
| PHP | post_max_size = 20M trong php.ini |
| ASP.NET Core | KestrelServerLimits.MaxRequestBodySize (mặc định 30 MB — đủ); IIS: maxAllowedContentLength |
| Node.js Express | express.json({ limit: '20mb' }) (mặc định 100 KB!) |
| Python FastAPI / Flask | mặc định không giới hạn; sau nginx thì xem dòng nginx |
| Cloudflare / API gateway | kiểm tra giới hạn kích thước body của dịch vụ |
5. Xác thực
Chủ camera khai báo tên header (mặc định Authorization) và giá trị (VD Bearer abc123, hoặc header
X-Api-Key với giá trị abc123). Camera gửi đúng nguyên văn header đó trong mọi yêu cầu.
- Cấp mỗi khách hàng / bãi xe một token riêng, so khớp token → biết dữ liệu thuộc khách nào.
- Có thể lọc thêm theo
camera_id: chỉ nhận các camera đã đăng ký cho token đó. - Dùng HTTPS khi URL đi qua Internet (token và ảnh đi trong yêu cầu).
- So sánh token bằng hàm so sánh thời gian cố định (
hash_equals,CryptographicOperations.FixedTimeEquals…).
6. Bỏ bản trùng (idempotent)
Camera bảo đảm "ít nhất một lần": nếu bạn đã lưu nhưng trả lời không tới được camera (mạng chập chờn, trả lời chậm
hơn thời gian chờ), camera gửi lại đúng bản ghi đó, cùng event_id. Hãy:
- Đặt
event_idlà khóa UNIQUE trong CSDL. - Gặp
event_idđã có → không lưu thêm, vẫn trả200(trả lỗi thì camera gửi lại mãi tới khi bỏ).
-- PostgreSQL (MySQL: INSERT IGNORE / ON DUPLICATE KEY UPDATE id=id; SQL Server: MERGE hoặc bắt lỗi 2627)
CREATE TABLE plate_events (
id BIGSERIAL PRIMARY KEY,
event_id VARCHAR(100) NOT NULL UNIQUE,
camera_id VARCHAR(32) NOT NULL,
device_id VARCHAR(64),
plate VARCHAR(24) NOT NULL,
plate_display VARCHAR(24),
raw_text VARCHAR(32),
confidence REAL,
read_count INT,
event_time TIMESTAMPTZ NOT NULL, -- từ "timestamp"
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
box INT[],
image_path TEXT, -- đường dẫn file ảnh toàn cảnh (đừng lưu base64 trong CSDL)
plate_image_path TEXT
);
CREATE INDEX ON plate_events (plate, event_time DESC);
CREATE INDEX ON plate_events (camera_id, event_time DESC);
INSERT INTO plate_events (event_id, camera_id, device_id, plate, plate_display, raw_text, confidence, read_count, event_time, box)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, to_timestamp($9), $10)
ON CONFLICT (event_id) DO NOTHING; -- bản trùng: không làm gì, vẫn trả 200
7. Lưu ảnh
- Giải mã base64 → ghi ra file (ổ đĩa / S3 / MinIO…), CSDL chỉ lưu đường dẫn.
- Đặt tên file theo
event_id(VDLPR-9MHK902C8J5Z-1792114200-000042_bien_so.jpg) — đừng đặt theo giờ + biển số: hai camera cùng thấy một xe trong cùng giây sẽ đè ảnh nhau. - Ảnh biển số ~10 KB/lượt; ảnh toàn cảnh nhỏ ~100 KB/lượt, ảnh nét tới 2 MB/lượt — tính dung lượng lưu trữ và đặt thời hạn xóa ảnh cũ.
8. Các bước xử lý gợi ý
1. Kiểm tra header xác thực → sai: 401
2. Đọc JSON, kiểm tra plate, timestamp → thiếu / sai dạng: 400
3. "test": true → 200, không lưu
4. INSERT ... ON CONFLICT (event_id) DO NOTHING (+ ghi file ảnh)
lỗi CSDL / ổ đĩa (tạm thời) → 503 (camera gửi lại)
5. Trả 200 {"ok":true} — ngay, trong < 1–2 giây
6. Việc khác (mở barie, đối chiếu vé tháng, thông báo...) chạy nền sau khi đã trả lời
Cả 4 mã mẫu trong vi_du_api/ làm đúng các bước này (lưu ra file data/ để chạy thử không cần CSDL):
| Chạy | |
|---|---|
C# — vi_du_api/csharp |
dotnet run --urls http://0.0.0.0:5000 |
Node.js — vi_du_api/node |
npm install && node server.js |
PHP — vi_du_api/php/plates.php |
php -S 0.0.0.0:5000 plates.php (hoặc chép lên hosting) |
Python — vi_du_api/python |
pip install fastapi uvicorn && uvicorn main:app --host 0.0.0.0 --port 5000 |
Đặt biến môi trường API_TOKEN=abc123 để bắt buộc header Authorization: Bearer abc123.
9. Kiểm tra API trước khi giao
- Tự kiểm tra bằng công cụ (đóng vai camera, chỉ cần Python 3):
Công cụ gửi: bản Gửi thử, bản có ảnh toàn cảnh + ảnh biển, gửi lại bản trùng, thiếu token, ảnh ~3 MB; báo ĐẠT / LỖI từng mục kèm cách sửa. (Tạo 2 bản ghi biểnpython vi_du_api/kiem_tra_api.py https://api.congty.vn/api/plates --header "Authorization: Bearer abc123"99Z-999.99, cameraLPR-KIEMTRA00000— xóa sau.) - Với camera thật: trên app → Cài đặt camera → Gửi dữ liệu biển số → nhập URL + token → Gửi thử. App hiện mã HTTP và nội dung bạn trả về. Màn này cũng hiện số bản ghi Đã gửi / Đang chờ / Lỗi và lỗi gần nhất.
- Thử mất kết nối: tắt API vài phút cho xe đi qua, bật lại → các bản ghi phải tới đủ, đúng thứ tự, không trùng.
10. Lỗi thường gặp
| Hiện tượng (app → Gửi dữ liệu biển số) | Nguyên nhân |
|---|---|
| "Không kết nối được …" | Camera không tới được máy chủ: sai IP/cổng, tường lửa (Windows Firewall), URL nội bộ nhưng camera ở mạng khác. |
| "Không phân giải được tên máy chủ (DNS)" | Tên miền sai / mạng của camera không có DNS. Thử dùng IP. |
| "Bắt tay TLS thất bại" | Đang bật Kiểm tra chứng chỉ HTTPS mà chứng chỉ tự ký / hết hạn / sai tên miền. |
| "Máy chủ trả mã HTTP 401/403" | Sai tên header hoặc giá trị token (lưu ý chữ Bearer có dấu cách). |
| "Máy chủ trả mã HTTP 404/405" | Sai đường dẫn, hoặc endpoint không nhận POST. |
| "Máy chủ trả mã HTTP 301/302/308" | URL bị chuyển hướng (http→https, thiếu / thừa / cuối) — sửa URL cho đúng địa chỉ cuối cùng. |
| "Máy chủ trả mã HTTP 413" | Giới hạn dung lượng (mục 4) — thường do nginx client_max_body_size. |
| "Máy chủ không trả lời trong N giây" | API xử lý chậm — trả lời trước, xử lý sau (mục 3). |
| Nhận trùng bản ghi | Bình thường khi mạng chập chờn — bỏ trùng theo event_id (mục 6). |
| Đang chờ tăng dần | API đang lỗi 5xx / quá giờ; camera vẫn giữ dữ liệu, sửa API xong tự gửi bù. |
11. Phiên bản
Định dạng này áp dụng cho phần mềm camera từ bản có event_id / plate_image trở đi (10/2026). Bản cũ hơn không có
hai trường đó — khi thiếu event_id, bỏ trùng theo camera_id + timestamp + plate.