1. 移除 routes/api.php 中的 B010 心跳上報路由。 2. 刪除 MachineController.php 中的 heartbeat 方法與相關過期邏輯。 3. 刪除 docs/b010_technical_spec.md 並更新相關技術規格文件。 4. 修改 ListenMqttQueue.php,移除獨立的溫度與版本號處理,統一由 heartbeat 任務分發。 5. 更新 MQTT 通訊規範,廢棄獨立的屬性 Topic (temperature, firmware_version)。
9.3 KiB
| name | description |
|---|---|
| MQTT 即時通訊與 Topic 規範 | 定義 Star Cloud 與終端機台 (IoT) 之間的 MQTT 全域通訊拓撲、主題 (Topic) 結構、資料載體 (Payload) 格式與安全性認證機制。 |
MQTT 即時通訊與 Topic 規範 (MQTT Protocol Specs)
本文件定義機台與 Star Cloud 之間的高併發即時通訊標準。MQTT 主要用於處理高頻率且對即時性要求高的訊息(如心跳、遠端指令),與 HTTP REST API 形成雙軌互補架構。
1. 連線基礎設定 (Connection Basics)
1.1 Broker 資訊
- 協議版本:MQTT v3.1.1 (相容性最高)
- Keep-Alive:建議設定為 30 ~ 60 秒。
各連線通道埠號:
| 通道 | 埠號 | 用途 | 使用者 |
|---|---|---|---|
| TCP | 1883 | Docker 內部 / 區網測試 | Go Gateway、開發除錯 |
| SSL/TLS | 8883 | 加密 TCP (正式環境) | Go Gateway (選配) |
| WebSocket | 8083 | EMQX 原生 WS 監聽 | NPM 反向代理目標 |
| WSS (公網) | 443 | App 端透過 NPM 反代連線 | Android / iOS App |
1.2 身份認證與識別 (Authentication & Identity)
機台連線時必須提供以下憑據:
- Username:機台編號 (
serial_no),例如SN00001。 - Password:機台
api_token的 SHA256 雜湊值,由 B014 API 取得。 - Client ID:格式建議為
SC_{serial_no}_{random_suffix}。- 解析邏輯:系統接收連線事件時,會自動移除
SC_前綴,並移除第一個底線後的所有隨機字串,還原出純淨的serial_no。 - 範例:
SC_M0408_pndk➜ 解析為M0408。
- 解析邏輯:系統接收連線事件時,會自動移除
EMQX 認證機制:
- 資料結構:Redis Hash (
HSET)。Key 格式為{prefix}machine_auth:{username},包含password欄位。 - 查詢指令:EMQX 使用
HMGET {prefix}machine_auth:${username} password進行驗證。 - Laravel 同步:透過
MachineService::syncMqttAuth()或批量指令php artisan mqtt:sync-auth維護。
Caution
嚴禁使用 Redis String (
SET) 儲存認證資料。EMQX 5.x 的 Redis Auth Plugin 僅支援HMGET讀取 Hash 結構。若誤用 String,認證將永遠失敗。
1.3 連線狀態即時同步 ($SYS Events)
系統透過訂閱 EMQX 的系統主題來實現「毫秒級」的連線狀態更新,不需等待心跳包:
- 訂閱主題:
$SYS/brokers/+/clients/+/connected➜ 標記為online$SYS/brokers/+/clients/+/disconnected➜ 標記為offline
- 處理流程:Go Gateway 接收 ➜ 解析 Client ID 為 Serial No ➜ 推送
type: status至 Redis ➜ LaravelProcessStatusJob 更新資料庫與last_heartbeat_at。
1.4 Go Gateway 認證
- Username:
star-cloud-gateway(固定值)。 - Password:與 Redis Hash
machine_auth:star-cloud-gateway中的password欄位對應。 - Go Gateway 的認證資料須透過 Laravel 側寫入 Redis。
1-A. App 端 WSS 連線規範 (WebSocket Secure)
App (Android) 透過 WSS 穿越公網連線 Broker。路徑:App → Cloudflare → NPM (port 8083 反代) → EMQX。
連線參數
| 參數 | 值 |
|---|---|
| URL | wss://mqtt.setdomains.work:443/mqtt |
| Username | {machineID} (即 serial_no) |
| Password | {api_token} 原始值(B014 取得,不做 SHA256) |
| Client ID | SC_{machineID}_{random} |
| Clean Session | true |
Caution
App 傳入原始 Token,EMQX 端的
password_hash_algorithm須設為sha256,由 Broker 自動雜湊比對 Redis 中的值。
Android 範例 (HiveMQ)
Mqtt3Client client = MqttClient.builder()
.useMqttVersion3()
.identifier("SC_" + machineID + "_" + randomSuffix)
.serverHost("mqtt.setdomains.work")
.serverPort(443)
.sslWithDefaultConfig()
.webSocketConfig().serverPath("/mqtt").applyWebSocketConfig()
.simpleAuth().username(machineID).password(apiToken.getBytes()).applySimpleAuth()
.willPublish()
.topic("machine/" + machineID + "/heartbeat")
.payload("{\"status\":\"offline\"}".getBytes())
.applyWillPublish()
.buildBlocking();
Fallback 策略
- 指數退避重連:1s → 2s → 4s → ... → 最大 60s。
- WSS 斷線超過 3 分鐘 → 退回 HTTP 輪詢 (B010),背景每 60 秒嘗試重連 WSS。
- WSS 恢復 → 立即切回 MQTT,停止 HTTP 輪詢。
2. 主題架構 (Topic Topology)
我們採用目錄式的層級結構,方便未來進行萬台設備的管理與 ACL 權限切分。
| 主題名稱 (Topic) | 方向 | QoS | 用途說明 |
|---|---|---|---|
machine/{serial_no}/heartbeat |
設備 ➜ 雲端 | 0 | 每 10 秒上報狀態大包裝(含心跳、溫度、韌體版本等)。 |
machine/{serial_no}/error |
設備 ➜ 雲端 | 1 | 發生硬體故障、卡貨或門未關時立即上報。 |
machine/{serial_no}/transaction |
設備 ➜ 雲端 | 1 | 交易完成、出貨結果的回報。 |
machine/{serial_no}/command |
雲端 ➜ 設備 | 1 | 雲端下發的即時指令(出貨、更新、重啟)。 |
3. 資料載體規範 (Payload Definitions)
所有 Payload 統一採用 JSON 格式,字母一律為 snake_case。
3.1 心跳上報 (Heartbeat) - machine/{id}/heartbeat
比照原 B010 邏輯,但去除不必要的 HTTP Header 開銷。
{
"current_page": 1,
"firmware_version": "1.0.5",
"temperature": 25.5,
"door_status": 0,
"timestamp": "2026-04-14T09:00:00+08:00"
}
3.2 異常上報 (Error/Event) - machine/{id}/error
比照原 B013 邏輯。
{
"tid": 12,
"error_code": "0403",
"log": "Slot jammed at slot 12",
"timestamp": "2026-04-14T09:05:00+08:00"
}
3.3 雲端指令 (Downstream Commands) - machine/{id}/command
這是雲端主動下發給機台的訊息,取代原本 B010 Response 的輪詢等待。
{
"command": "dispense",
"payload": {
"slot_no": 5,
"transaction_id": "T202604140001"
},
"message_id": "MSG_123456789"
}
常用指令集:
reboot: 機台重啟。reload_config: 重新下載參數 (B014)。reload_products: 重新同步商品 (B012)。dispense: 遠端出貨指令 (B055)。
3.4 屬性上報 (Attribute Updates)
過去曾支援獨立的 temperature 與 firmware_version Topic,現已廢棄。
機台應將所有屬性更新集中於 machine/{serial_no}/heartbeat 的 JSON Payload 中發送,以降低連線開銷並簡化後端處理。
Important
集中化原則:除非極特殊的即時性需求,否則所有機台狀態(溫度、版本、門狀態、頁面碼)應統一包在 Heartbeat 訊息中。
4. 安全與 QOS 規範
- 存取控制 (ACL):EMQX Broker 必須設定 ACL,禁止機台 A 訂閱機台 B 的 Topic。機台僅能訂閱與發布包含自身
serial_no的路徑。 - QoS 策略:
- QoS 0:適用於高頻率心跳,即使掉一兩次包也不影響系統判斷。
- QoS 1:適用於交易與指令,確保「至少送達一次」。App 端收到指令後應回覆回執。
- 遺囑訊息 (Last Will and Testament):
機台 Connect 時應設定 Last Will 於
machine/{serial_no}/heartbeat,Payload 為{"status": "offline"}。當連線異常中斷時,雲端能立刻得知。 - 傳輸加密:
- App 端 (公網):強制使用 WSS (
wss://),TLS 由 NPM / Cloudflare 終止。 - Go Gateway (內網):預設走 TCP (
tcp://emqx:1883),區網環境可免 TLS。正式環境建議升級至ssl://emqx:8883。
- App 端 (公網):強制使用 WSS (
5. Redis 橋接注意事項
5.1 Key 前綴一致性
Laravel 的 Redis 驅動會自動在 Key 前加上前綴 (預設 starcloud_database_)。Go Gateway 不使用 Laravel Redis 客戶端,因此必須在程式碼中手動加上此前綴:
// main.go
prefixedIncomingKey := "starcloud_database_" + cfg.IncomingQueueKey
pusher := bridge.NewRedisPusher(rdb, prefixedIncomingKey)
Caution
若 Go 與 Laravel 操作不同前綴的 Key,訊息將永遠無法傳達。修改 Laravel 的
config/database.php中 Redis prefix 後,必須同步修改 Go 端的前綴字串。
5.2 長連線穩定性
Laravel mqtt:listen 使用 BLPOP 長時間阻塞等待。為防止 PHP 端主動關閉連線,必須在 config/database.php 設定:
'redis' => [
'default' => [
'read_timeout' => -1, // 永不超時
'timeout' => 10.0, // 初始連線超時
],
],
6. 與 REST API 的同步關係
App 端的通訊策略為 MQTT 優先、HTTP 兜底:
| 狀態 | 心跳 | 指令接收 | 交易回報 |
|---|---|---|---|
| WSS 連線正常 | MQTT 發布 (10s) | MQTT 訂閱 | MQTT 發布 |
| WSS 斷線 < 3 分鐘 | 暫停,等待重連 | 不可用 | 暫存本地,重連後補發 |
| WSS 斷線 ≥ 3 分鐘 | HTTP B010 輪詢 | 不可用 (降級) | HTTP B600 回報 |
| WSS 恢復 | 切回 MQTT | 恢復 MQTT 訂閱 | 切回 MQTT |
7. 常用維運指令
| 指令 | 用途 |
|---|---|
sail artisan mqtt:sync-auth |
將所有機台認證資料從 MySQL 同步至 Redis Hash |
sail artisan mqtt:listen |
啟動 MQTT 訊息消費者 (常駐執行) |
docker compose build mqtt-gateway |
重新編譯 Go Gateway |
docker restart star-cloud-mqtt-gateway |
重啟 Gateway 容器 |
docker restart star-cloud-queue |
重啟 Queue Worker (程式碼變更後必須執行) |