# 16 — SCADA gateway (ESP32 đọc PLC, gửi số đo mỗi giây)
[English](README.md) | [Tiếng Việt](README_vi.md)
ESP32 đặt trong mạng điều khiển nhà máy. Mỗi giây nó đọc PLC/SCADA qua
**Modbus TCP**, gửi ảnh chụp số đo lên cloud (`telemetry`), và báo động khi một tag
vượt ngưỡng. Dashboard kiểu "sơ đồ vận hành" (Hz/A từng động cơ, nhiệt độ, điện 3
pha) đọc dữ liệu từ cloud.
```
PLC / SCADA ──Modbus TCP (chỉ đọc)──► ESP32 ──WebSocket ra ngoài──► cloud ──► dashboard
```
**Chỉ đọc.** Không có lệnh nào từ cloud ghi xuống PLC hay biến tần. Lệnh duy nhất
gateway nhận là `config_updated` (tải lại bảng tag). Điều khiển máy là việc của
SCADA tại chỗ.
## Mạng — đọc trước khi lắp
ESP32 phải tới được **cả hai**: PLC (IP trong mạng điều khiển) và Internet (cloud).
Mạng điều khiển thường **không** có Internet. Các cách thường dùng:
| Cách | Khi nào |
|---|---|
| WiFi vào một mạng thấy được PLC và có đường ra Internet | đơn giản nhất nếu IT cho phép |
| Ethernet (W5500/LAN8720) vào mạng PLC + WiFi/4G ra Internet | mạng điều khiển cách ly — *chưa có trong example này* |
Gateway **chỉ gọi ra**: không mở cổng nào để bên ngoài kết nối vào mạng nhà máy.
## Bảng tag
Mặc định biên dịch sẵn trong `main/scada_tags.c` (8 tag mẫu theo sơ đồ khu mủ vào —
**địa chỉ là giả**, thay bằng bảng địa chỉ thật của PLC). Cách nên dùng là gửi khoá
`scada` trong cấu hình vận hành từ cloud. ESP32 cache vào NVS, đổi tag không cần
flash lại:
```json
{"scada":{
"host":"192.168.10.5", "port":502, "interval_ms":1000,
"tags":[
["MLM5.hz", 1, 3, 100, "u16", 0.01],
["MLM5.a", 1, 3, 101, "u16", 0.01, 10],
["CM2.temp1", 1, 3, 200, "s16", 0.1, 80],
["grid.volt_ab", 1, 3, 300, "f32", 1, 420, 360, true]
]}}
```
Cột: `tên, unit id, mã hàm (1–4), địa chỉ, kiểu (u16 s16 u32 s32 f32 bit), hệ số,
[ngưỡng cao, ngưỡng thấp, đảo word]`. Tên tag tối đa 32 ký tự `[A-Za-z0-9_.-]`.
Một khung telemetry tối đa ~3.5 KB, đủ cho khoảng 100 tag. Nhiều hơn thì tách
thành nhiều khung (chưa làm trong example).
## Cảnh báo
- `<tag>_high` / `<tag>_low` (warning): bật khi chạm ngưỡng, tắt khi đã lùi qua
ngưỡng 2% (tối thiểu 0.5). Giá trị dao động quanh ngưỡng không bật/tắt mỗi giây.
- `scada_source_down` (critical): không đọc được tag nào trong một nhịp.
- Mất mạng: cảnh báo được giữ và gửi lại khi kết nối.
## Build
```bash
idf.py set-target esp32s3
idf.py menuconfig # SCADA gateway → host/port mặc định (khi cloud chưa gửi cấu hình)
idf.py flash monitor
```
Chạy thử không cần cloud thật: `go run ./tools/mock-cloud`, rồi mở
`http://localhost:8080/telemetry` để xem số đo cập nhật trực tiếp.
## Đã kiểm
- `tests/test_modbus_tcp.c`: client Modbus TCP với server giả qua socket thật. Gồm
các trường hợp: đọc thanh ghi/bit, f32/s16/u32, exception, lệch transaction, mất
kết nối rồi tự nối lại, từ chối mọi mã hàm ghi.
- `tests/test_scada_alarm.c`: ngưỡng có vùng trễ.
- mock-cloud `TestTelemetrySnapshot`: nhận khung đúng spec, bỏ khung sai.
- `idf.py build` ESP-IDF 5.5.4.
- **Chưa chạy trên board với PLC thật.**
To create a project from this example, run:
idf.py create-project-from-example "nguyenduchoai/innoedge=0.3.0:16-scada-gateway"