# Đầu vào MQTT dừng hoặc lỗi

> Một datasource đầu vào MQTT chuyển sang idle giữa luồng, dự đoán đọc ra `NaN`, hoặc sink đầu ra MQTT ghi log "transport not connected" — năm nguyên nhân gốc rễ khác biệt và cách phân biệt chúng.

## Triệu chứng

Một datasource dựa trên MQTT đang truyền dữ liệu, và có gì đó bị gãy:

- **Suy luận tự chuyển từ đang chạy sang idle/lỗi** — không ai bấm Stop.
- **Dự đoán trả về `NaN`**, hoặc bảng điều khiển không hiện gì hợp lý.
- **Sink đầu ra MQTT báo lỗi ghi**, với lý do như `transport: not connected`.
- **Một ánh xạ tag bị từ chối lúc lưu** với thông báo `tag_outside_subscription_*`.
- **Test Connection hoặc Enable thất bại hoàn toàn** trước khi có bất kỳ luồng nào chạy.

MQTT là một giao thức **push** — suy luận **pull** một mẫu mỗi tick từ một cache mà
adapter điền vào khi message đến. Mỗi nguyên nhân dưới đây thực chất là cùng một câu hỏi
nhìn từ góc khác: *cache có nhận được một giá trị dùng được gần đây không?*

## Xác nhận

Năm chế độ lỗi khác biệt cùng chia sẻ tập triệu chứng này. Grep log backend để phân biệt
chúng — tất cả các ví dụ dưới đây chỉ hiện mã lỗi/lý do, không bao giờ hiện nội dung
payload hay thông tin xác thực.

```bash
docker compose -f docker-compose.release.yml logs backend | grep -iE "MqttAdapter|ChannelReadException|channel .* read failed|tag stale|disconnected|not connected"
```

1. **Topic cũ (stale) (publisher chậm hoặc đã chết).** Một cảnh báo xuất hiện trước khi
   lỗi:

   ```
   MqttAdapter tag stale: tag=<topic> age=<age> staleAfter=<window>
   Channel <index> (<tag>) read failed — stopping inference
   Inference fault: <reason>
   ```

   Cửa sổ hết hạn của cache là `clamp(50 / SamplingHz, 5s, 60s)` — **5 giây** ở mức
   mặc định 10 Hz. Một topic publish chậm hơn cửa sổ đó sẽ hết hạn và gây ra
   `ChannelReadException`, mà runtime xử lý giống hệt một cảm biến đã chết: nó dừng
   suy luận thay vì đưa cho mô hình một giá trị đông cứng.

2. **Sentinel `NaN` / `Infinity`.** Bộ phân tích payload MQTT chỉ chấp nhận các số
   *hữu hạn* — `NaN`, `Infinity`, `-Infinity`, và một literal tràn số như `1e400` đều
   bị từ chối trước khi chúng đến được cache. Một thiết bị publish sentinel "không có
   phép đo" theo cách này không bao giờ làm mới giá trị đã cache của nó, nên nó hết hạn
   và lỗi **theo đúng cách giống hệt** như nguyên nhân 1 ở trên — cùng dòng log, cùng
   `ChannelReadException`. Theo thiết kế, không có chế độ lỗi riêng "NaN đã đến được mô
   hình" — điều đó sẽ âm thầm đầu độc mọi dự đoán downstream thay vào đó.

3. **Trục xuất client id (eviction).** Tìm:

   ```
   MqttAdapter disconnected (SessionTakenOver); reconnecting
   ```

   hoặc, ở phía đầu ra, một lần ghi thất bại với:

   ```
   transport: not connected
   ```

   Các broker MQTT trục xuất kết nối *đang giữ chỗ* khi một client thứ hai kết nối với
   cùng một client id. Trên bản build hiện tại, điều này chỉ xảy ra khi: **hai datasource
   cùng vai trò** (hai đầu vào, hoặc hai đầu ra) chia sẻ một **Client ID** tường minh, hoặc
   một **client MQTT bên ngoài** dùng lại id đã cấu hình của một datasource.

   
**Cặp đầu vào/đầu ra trên cùng một cấu hình không còn xung đột (v1.28.0)**

   Một **Client ID** đã cấu hình sẽ tự động có hậu tố theo vai trò tại thời điểm kết nối
   (`<id>-in` cho adapter đầu vào, `<id>-out` cho sink đầu ra) — điều này áp dụng cho mọi
   cấu hình đã lưu, kể cả một cấu hình cũ vẫn còn mang mặc định `aiboard` chia sẻ, nên một
   đầu vào và đầu ra MQTT được xây từ *cùng* một cấu hình datasource không còn có thể trục
   xuất lẫn nhau. Nếu bạn vẫn thấy điều này trên một cặp đầu vào/đầu ra, bạn đang dùng một
   build cũ hơn v1.28.0 — hãy nâng cấp thay vì tự sửa tay id.
   

4. **Tag nằm ngoài Base Topic đã đăng ký.** Lưu một ánh xạ tag (hoặc bật datasource)
   thất bại ngay lập tức với:

   ```
   tag_outside_subscription_<tagRef>
   ```

   Adapter đầu vào chỉ đăng ký các tag đã ánh xạ của nó (hoặc wildcard **Base Topic**
   đã cấu hình) — một đường dẫn topic nằm ngoài phạm vi đó bị từ chối ngay lúc lưu thay
   vì âm thầm không bao giờ tạo ra một lần đọc.

5. **Lỗi kết nối ở cấp socket.** Test Connection hoặc Enable thất bại trước khi bất kỳ
   luồng nào bắt đầu, với một trong các mã:

   connect_failed không thể tới / bị từ chối / lỗi DNS &nbsp;
   timeout lần thử kết nối bị hủy trước khi hoàn tất

   Log backend xác nhận điều này bằng thời gian đã trôi qua và loại exception:

   ```
   MqttAdapter connect failed: broker=<broker> elapsedMs=<ms> errorType=<type>
   ```

   
**Bẫy localhost trong Docker**

   Bên trong một container, `localhost` nghĩa là **chính container backend**, không
   phải desktop hay Docker host của bạn. Một broker mà một MQTT client trên desktop
   tới được tốt ở `localhost:1883` có thể không tới được từ AIBOARD ở cùng địa chỉ đó
   — hãy dùng tên service/container của broker (ví dụ `mqtt://mosquitto:8884` trên một
   mạng Docker) hoặc `host.docker.internal` thay vào đó.
   

## Sửa

1. **Topic cũ / sentinel NaN (nguyên nhân 1–2).** Sửa nhịp publish, hoặc dừng nó publish
   một sentinel không hữu hạn, rồi bật lại datasource — một lỗi kéo runtime xuống idle,
   nên phục hồi không tự động:
   - Xác nhận khoảng publish thực tế của topic đã ánh xạ và so sánh nó với cửa sổ hết
     hạn ở **Sampling Hz** đã cấu hình của datasource.
   - Nếu topic báo cáo hợp lệ là "không có phép đo", hãy chuyển thiết bị/gateway sang
     đơn giản là **không publish** trong khoảng thời gian đó thay vì gửi
     `NaN`/`Infinity` — cả hai được xử lý giống hệt nhau (một topic im lặng), nên
     không có lợi ích gì khi gửi sentinel.
   - Bật lại datasource (nạp lại mô hình, kết nối lại adapter), rồi bấm **Start**.

2. **Trục xuất client id (nguyên nhân 3).** Đặt một giá trị khác biệt cho mỗi datasource
   cùng vai trò (hai đầu vào, hoặc hai đầu ra) đang chia sẻ một **Client ID** tường minh,
   hoặc xóa trống trường đó để MQTTnet tự sinh một id duy nhất cho mỗi bên. Nếu một client
   bên ngoài đang dùng lại id của datasource của bạn, hãy đổi một trong hai. Điều này
   **không** áp dụng cho một cặp đầu vào/đầu ra được xây từ cùng một cấu hình — hậu tố vai
   trò của v1.28.0 (`-in`/`-out`) đã giữ cho chúng không xung đột, không cần chỉnh sửa.

3. **Tag ngoài phạm vi đăng ký (nguyên nhân 4).** Hoặc mở rộng **Base Topic** của
   datasource để bao phủ đường dẫn topic của tag, hoặc sửa đường dẫn topic của tag để
   nằm trong Base Topic hiện tại. Lưu lại ánh xạ.

4. **Lỗi socket (nguyên nhân 5).** Xác minh host, port, và khả năng tới được của mạng
   từ *bên trong* container backend — không phải từ desktop của bạn. Đổi `localhost`
   thành tên container/service của broker (hoặc `host.docker.internal` từ một
   container tới Docker host), rồi thử lại **Test Connection**.

## Phòng ngừa

- **Kiểm tra khoảng publish của mọi topic đã ánh xạ so với cửa sổ hết hạn** trước khi
  bật một đầu vào MQTT mới — giảm **Sampling Hz** để nới rộng ngưỡng chịu đựng
  (1 Hz → 50s) nếu một topic hợp lệ publish chậm.
- **Không bao giờ dùng lại một Client ID tường minh giữa hai datasource cùng vai trò**
  (hai đầu vào, hoặc hai đầu ra), và đừng dùng lại Client ID của một datasource cho một
  client MQTT bên ngoài. Một cặp đầu vào/đầu ra trên cùng một cấu hình đã an toàn sẵn —
  v1.28.0 tự động thêm hậu tố cho mỗi bên (`-in`/`-out`).
- **Giới hạn Base Topic chặt chẽ** theo đúng các tag bạn thực sự ánh xạ, và chỉ ánh xạ
  các tag có đường dẫn topic đã nằm trong đó.
- **Đừng mã hóa "không có dữ liệu" thành `NaN`/`Infinity` trên đường truyền.** Một thiết
  bị im lặng khi không có gì để báo cáo hoạt động giống hệt và dễ suy luận hơn một giá
  trị sentinel.
- **Chạy Test Connection từ góc nhìn của box**, không phải từ máy trạm của bạn — một
  broker có thể tới được từ desktop qua `localhost` có thể hoàn toàn không tới được từ
  container backend.

## Liên quan

- [Thông báo](/vi/operate/notifications/) — cảnh báo dừng ngoài ý muốn và thông báo lỗi
  ghi đầu ra hiển thị các lỗi này trong UI.
- [Kết nối nguồn dữ liệu đầu vào](/vi/configure/input-datasources/) — cửa sổ hết hạn,
  hành vi kết nối lại, và từ chối giá trị không hữu hạn đầy đủ.
- [Datasource đầu ra](/vi/configure/output-datasources/) — QoS publish MQTT, hậu tố
  client id, và chẩn đoán Test Connection.
- [Datasource sập hoặc lỗi](/vi/troubleshooting/datasource-down-or-faulted/) — runbook
  Enable/lỗi tổng quát mà trang này chuyên biệt hóa cho MQTT.
