[REFACTOR] 廢棄 B010 HTTP API 並整合 MQTT 心跳回報流程

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)。
This commit is contained in:
sky121113 2026-04-21 15:48:49 +08:00
parent bdc1c44c0f
commit d0f84eb82b
9 changed files with 14 additions and 244 deletions

View File

@ -11,7 +11,7 @@ trigger: always_on
本系統 API 分為兩大類,遵循不同的設計慣例:
* **Admin/Web API (`/api/v1/...`)**: 供後台管理介面、APP UI 使用。遵循標準 RESTful 與 JSON 結構。
* **Machine IoT API (`/api/app/...`)**: 供販賣機、計時器等硬體端點使用。需相容既有 PDF 規格(如 B010, B600欄位命名多為 `req1`, `req2` 或特定縮寫。
* **Machine IoT API (`/api/app/...`)**: 供販賣機、計時器等硬體端點使用。需相容既有 PDF 規格(如 B017, B600欄位命名多為 `req1`, `req2` 或特定縮寫。
---
@ -21,7 +21,7 @@ trigger: always_on
* **Machine IoT API**:
* **核心機制**: 必須在 Header 帶入 `Authorization: Bearer <api_token>` 進行身份驗證。
* **Phase 1 (兼容模式)**: 若為相容既有機動硬體,可暫時接受 Request 包含 `key` 欄位,但後端應過渡至 Bearer 驗證。
* **安全性強化**: 改用每台機台專屬的 `api_token` (透過 B010 初始化或派發),並配合 `serial_no` (即 `workid`) 進行資料歸屬權驗證。
* **安全性強化**: 改用每台機台專屬的 `api_token` (透過 B014 初始化或派發),並配合 `serial_no` (即 `workid`) 進行資料歸屬權驗證。
* **傳輸安全**: 必須強制使用 **HTTPS**
---
@ -38,7 +38,7 @@ trigger: always_on
}
```
### 3.2 IoT 指令回應 (B010/B055 等)
### 3.2 IoT 指令回應 (B017/B055 等)
機台端通常透過 response 的 `status` 欄位或特定的 `message` 字串來執行動作:
* **成功但有指令**: `{"status": "49", "message": "reload B017"}`
* **純資料回傳**: 直接返回對象陣列或 PDF 定義的欄位。
@ -54,12 +54,11 @@ trigger: always_on
* 遵循 **kebab-case** 路由與 **snake_case** JSON 欄位。
### 4.2 終端類 (IoT / Machine) — 須嚴格遵守 PDF 規格
* **API 識別碼 (workid)**: URL 中的 `{workid}` 參數固定為該 API 的功能代碼 (如 `B010`, `B017`, `B600`),不隨機台改變。
* **API 識別碼 (workid)**: URL 中的 `{workid}` 參數固定為該 API 的功能代碼 (如 B017, B600),不隨機台改變。
* **機台識別方式**:
1. **Header (推薦)**: 透過 `Authorization: Bearer <api_token>` 識別。針對 B017 等端點,雲端將自動關聯對應機台,**不需**額外帶入機台識別參數。
2. **Request Body (相容/特定模式)**: 透過 `machine``serial_no` 等欄位識別。主要用於 B000 登入或尚未取得 Token 的引導階段 (如 B014)。
* **主要 Endpoint 範例**:
* **心跳上報 (B010)**: `POST /api/app/machine/status/B010`
* **交易回傳 (B600)**: `POST /api/app/B600` (Body 欄位 `req2` 為機台編號)
* **貨道庫存 (B017)**: `GET /api/app/machine/reload_msg/B017`
* **遠端出貨 (B055)**: `POST /api/app/machine/dispense/B055`
@ -72,7 +71,7 @@ trigger: always_on
### 5.1 MQTT 通訊端點 (高頻與事件驅動)
以下高頻或即時事件,未來將**全面改採 MQTT 協議**,透過 EMQX 與 Go Gateway 橋接:
1. **B010 (心跳)**:機台發布至 `machine/{serial_no}/heartbeat`
1. **心跳事件**:機台發布至 `machine/{serial_no}/heartbeat`
2. **B013 (錯誤與狀態)**:機台發布至 `machine/{serial_no}/error`
3. **B600 / B602 (交易紀錄)**:機台發布至 `machine/{serial_no}/transaction`

View File

@ -21,7 +21,7 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
- **Header**`Authorization: Bearer <user_token>`。
### 2.2 機台通訊認證 (Machine Authentication)
- **適用 API**B010, B012, B013, B600 等後續通訊。
- **適用 API**B012, B013, B600 等後續通訊。
- **方式**:使用機台專屬之 **api_token**
- **Header**`Authorization: Bearer <api_token>`。
@ -118,54 +118,6 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
---
### 3.4 B010: 心跳上報與狀態同步
用於確認機台在線狀態、更新感測數據、提交事件日誌並獲取雲端指令。
- **URL**: POST /api/v1/app/machine/status/B010
- **Authentication**: Bearer Token (Header)
- **Request Body:**
| 參數 | 類型 | 必填 | 說明 | 範例 |
| :--- | :--- | :--- | :--- | :--- |
| current_page | Integer | 是 | 當前頁面代碼 (見下表) | 1 |
| firmware_version | String | 是 | 韌體版本號 | 1.0.5 |
| model | String | 是 | 機台型號 | STAR-V1 |
| temperature | Float | 否 | 環境溫度 | 25.5 |
| door_status | Integer | 否 | 門狀態 (0:關 / 1:開) | 0 |
| log | String | 否 | 事件日誌簡述 | Door opened |
| log_level | String | 否 | info, warn, error | info |
| log_payload | Object | 否 | 額外日誌 JSON 對象 | {"code":500} |
- **Response Body:**
| 參數 | 類型 | 說明 | 範例 |
| :--- | :--- | :--- | :--- |
| success | Boolean | 請求是否處理成功 | true |
| code | Integer | 內部業務狀態碼 | 200 |
| message | String | 回應訊息 | OK |
| status | String | **雲端指令代碼** (見下表) | 49 |
#### B010 代碼對照表
**頁面代碼 (current_page)**
- 0: 離線 / 1: 主頁面 / 2: 販賣頁 / 3: 管理頁
- 4: 補貨頁 / 5: 教學頁 / 6: 購買中 / 7: 鎖定頁
- 60: 出貨成功 / 61: 貨道測試 / 62: 付款選擇
- 63: 等待付款 / 64: 出貨 / 65: 收據簽單
- 66: 通行碼 / 67: 取貨碼 / 68: 訊息顯示
- 69: 取消購買 / 610: 購買結束 / 611: 來店禮
- 612: 出貨失敗
**雲端指令代碼 (status)**
- 49: reload B017 (貨道同步)
- 51: reboot (重啟系統)
- 60: reboot card machine (刷卡機重啟)
- 61: checkout (觸發結帳)
- 70: unlock (解鎖)
- 71: lock (鎖定)
- 85: reload B0552 (遠端出貨)
---
### 3.5 B012: 商品配置與商品主檔同步 (Unified Sync)
用於機台端獲取目前所有可販售商品的詳細配置。App 端應依據呼叫的方法決定數據處理方式。

View File

@ -100,9 +100,7 @@ Mqtt3Client client = MqttClient.builder()
| 主題名稱 (Topic) | 方向 | QoS | 用途說明 |
| :--- | :--- | :--- | :--- |
| `machine/{serial_no}/heartbeat` | 設備 ➜ 雲端 | 0 | 每 10 秒上報心跳、溫度、目前的頁面碼 (標準大包裝)。 |
| `machine/{serial_no}/temperature` | 設備 ➜ 雲端 | 0 | **溫度即時更新**。支援純數值 Payload (如 25.5)。 |
| `machine/{serial_no}/firmware_version` | 設備 ➜ 雲端 | 1 | **韌體版本上報**。變更時發送,支援純字串 (如 1.2.0)。 |
| `machine/{serial_no}/heartbeat` | 設備 ➜ 雲端 | 0 | 每 10 秒上報狀態大包裝(含心跳、溫度、韌體版本等)。 |
| `machine/{serial_no}/error` | 設備 ➜ 雲端 | 1 | 發生硬體故障、卡貨或門未關時立即上報。 |
| `machine/{serial_no}/transaction` | 設備 ➜ 雲端 | 1 | 交易完成、出貨結果的回報。 |
| `machine/{serial_no}/command` | 雲端 ➜ 設備 | 1 | 雲端下發的即時指令(出貨、更新、重啟)。 |
@ -158,15 +156,12 @@ Mqtt3Client client = MqttClient.builder()
- `dispense`: 遠端出貨指令 (B055)。
### 3.4 屬性上報 (Attribute Updates)
針對特定的屬性 Topic支援 **純文字/純數值 (Raw Value)** 格式以降低機台解析負荷
過去曾支援獨立的 `temperature``firmware_version` Topic現已**廢棄**
- **溫度上報** (`machine/{sn}/temperature`)
- Payload: `26.5` (純數字)
- **版本上報** (`machine/{sn}/firmware_version`)
- Payload: `1.2.5` (純字串)
機台應將所有屬性更新集中於 `machine/{serial_no}/heartbeat` 的 JSON Payload 中發送,以降低連線開銷並簡化後端處理。
> [!TIP]
> 雲端現在支援「傳統 JSON」與「簡潔 Raw Value」兩種發報模式。推薦使用新式 Attribute Topic 進行特定欄位更新
> [!IMPORTANT]
> **集中化原則**:除非極特殊的即時性需求,否則所有機台狀態(溫度、版本、門狀態、頁面碼)應統一包在 Heartbeat 訊息中。
---

View File

@ -86,7 +86,7 @@ Star Cloud 是一個專為智能販賣機設計的後台管理系統,負責管
### 2. Machine IoT API (`/api/app/...`)
- **對象**: 智能販賣機、計時器等硬體。
- **認證**: Header `Authorization: Bearer <api_token>`
- **高併發處理**: 核心日誌 (B010 心跳、B600 交易) **嚴禁直寫 DB**,必須進入 **Redis Queue** 背景異步處理。
- **高併發處理**: 核心日誌 (MQTT 心跳、B600 交易) **嚴禁直寫 DB**,必須進入 **Redis Queue** 背景異步處理。
### 3. MQTT IoT 通訊
- **對象**: 高頻/即時性機台通訊 (心跳、異常、交易)。

View File

@ -78,12 +78,6 @@ class ListenMqttQueue extends Command
}
switch ($type) {
case 'temperature':
case 'firmware_version':
// 將單一屬性包裹成 ProcessHeartbeat 認識的格式
Log::debug("MQTT Listen: Dispatching attribute update", ['serial_no' => $serialNo, 'type' => $type, 'payload' => $payload]);
ProcessHeartbeat::dispatch($serialNo, [$type => $payload]);
break;
case 'heartbeat':
// 確保 $payload 是陣列,若機台誤傳純數值則包裹之
$finalPayload = is_array($payload) ? $payload : ['raw_data' => $payload];

View File

@ -17,71 +17,6 @@ use Illuminate\Support\Facades\Cache;
class MachineController extends Controller
{
/**
* B010: Machine Heartbeat & Status Update (Asynchronous)
*/
public function heartbeat(Request $request)
{
$machine = $request->get('machine');
$data = $request->except(['machine', 'key']);
// 異步處理狀態更新 (現在日誌比對統一移至 ProcessHeartbeat)
\App\Jobs\Machine\ProcessHeartbeat::dispatch($machine->serial_no, $data);
// 取出待處理指令
$command = \App\Models\Machine\RemoteCommand::where('machine_id', $machine->id)
->pending()
->first();
$status = '49'; // 預設 49 (OK / No Command)
$message = 'OK';
if ($command) {
switch ($command->command_type) {
case 'reboot':
$status = '51';
$message = 'reboot';
break;
case 'reboot_card':
$status = '60';
$message = 'reboot card machine';
break;
case 'checkout':
$status = '61';
$message = 'checkout';
break;
case 'lock':
$status = '71';
$message = 'lock';
break;
case 'unlock':
$status = '70';
$message = 'unlock';
break;
case 'change':
$status = '82';
$message = $command->payload['amount'] ?? '0';
break;
case 'dispense':
$status = '85';
$message = $command->payload['slot_no'] ?? '';
break;
case 'reload_stock':
$status = '49';
$message = 'reload B017';
break;
}
// 標記為已發送 (sent)
$command->update(['status' => 'sent', 'executed_at' => now()]);
}
return response()->json([
'success' => true,
'code' => 200,
'message' => $message,
'status' => $status
], 202); // 202 Accepted
}
/**
* B055: Get Remote Dispense Queue (POST/GET)

View File

@ -65,38 +65,6 @@
---
## 🟢 B010: 心跳上報與狀態同步 (Heartbeat & Status)
機台定時(建議每 5-10 秒)上送,用於確認連線狀態、溫度及門禁狀態。
### 1. API 資訊
- **Endpoint**: `POST /api/v1/app/machine/status/B010`
- **認證方式**: Bearer Token (或 Request Body 帶 `key`)
- **處理方式**: 異步處理 (Redis Queue),立即回傳 202。
### 2. 請求範例 (JSON)
```json
{
"temperature": 5.2,
"door_status": 0,
"current_page": "home",
"firmware_version": "1.0.5",
"log": "Status heartbeat test",
"log_level": "info"
}
```
### 3. 回應規格
- **成功**: `202 Accepted`
```json
{
"success": true,
"code": 200,
"message": "OK",
"status": "49"
}
```
---
## 🔵 B600: 交易紀錄上報 (Transaction Record)
當機台端完成交易(收款或扣點成功)後上傳。
@ -137,12 +105,4 @@
- [ ] 資料庫 `order_items` 產生紀錄
- **結果**: 準備中...
### 2026-03-16: B010 首次演練測試
- **測試機台**: `SN001`
- **測試方式**: `curl` 命令模擬
- **驗證項目**:
- [x] HTTP 回傳 `202`
- [x] 資料庫 `machines.last_heartbeat_at` 更新為台北時間
- [x] `machine_logs` 產生對應日誌
- **測試備註**: 過程中發現 `current_page``tinyInteger` 導致寫入失敗,已將其修改為 `string` 以支援彈性的頁面名稱。
- **結果**: ✅ **成功 (SUCCESS)**。機台狀態已同步為 5.2度當前頁面「home」。

View File

@ -1,65 +0,0 @@
# B010 API (機台心跳與指令) 技術規範與執行指南
本文件整合了 B010 API 的業務邏輯討論與技術執行分析,作為 Star Cloud 實作機台通訊核心的指導準則。
---
## 1. 業務邏輯與指令下發 (Command Flow)
### 1.1 指令下發機制
B010 是 Cloud 向機台下達遠端指令(如:開鎖、重啟、改庫存)的唯一通道。
* **指令隊列**:指令需先寫入 `remote_commands` 表,狀態標記為 `pending`
* **撈取邏輯**:當機台定期呼叫 B010 時,後端 Service 會查詢該機台是否有等待執行的指令。
* **回應處理**:若有指令,則在 Response 的 `status` 欄位回傳對應的指令碼(如 51:重啟, 70:開鎖)。
* **優先順序**:當同時有多個指令待執行時,需按緊急程度(如:重啟 > 開鎖 > 載入配置)定義優先順序回傳。
### 1.2 執行回饋 (Feedback Loop)
* **確認機制**:機台收到指令後,必須確保執行結果能回報至雲端。
* **判定方案**
* **被動判定**:若機台在其後的 B010 心跳中回報狀態正常(如:軟體版本更新成功、頁面碼正確),即視為成功。
* **主動判定**:對於關鍵指令(如遠端出貨),機台需另外呼叫狀態更新 API (如 B055 PUT) 回報結果。
---
## 2. 執行面挑戰:高併發與效能 (Performance)
### 2.1 寫入瓶頸分析
* **數據規模**10,000 台機台,每 10 秒一個心跳,全站 QPS 約 1,000。
* **DB 壓力**:直接同步寫入 MySQL `machines``machine_logs` 將導致磁碟 I/O 鎖定。
### 2.2 解決策略 (Redis 緩衝)
* **即時狀態流動**
* **最後在線時間**、**溫度**、**門禁狀態**、**頁面碼** 等動態數據優先存入 **Redis**
* **批量更新 (Lazy Update)**:每 1~5 分鐘由後台任務將 Redis 數據批量寫回 MySQL `machines` 表。
* **日誌優化**
* **正常心跳不寫入資料庫日誌**,僅在**狀態變更**(如:門被打開、溫度過高、軟體報錯)時,才觸發資料庫日誌記錄。
---
## 5. 安全性與驗證策略 (Security)
* **API Key 遷移**:從目前全系統硬編碼的共用 Key逐步遷移至每台機台專屬的 `api_token`
* **驗證方式**:機台端需在 Header 帶入 `Authorization: Bearer <api_token>` 進行身份驗證。
* **實裝方式**
* 後台管理介面新增「核發機台 Token」功能。
* 建立專屬 `IotAuthMiddleware`,驗證請求中的 Token 與 `machines` 表是否匹配。
---
## 6. 離線與告警機制 (Heartbeat Logic)
* **在線判定**:心跳超時超過 **30 秒** 即在 UI 將機台顯示為「離線」。
* **告警機制**:機台斷線時,系統需即時產出**警示或推播通知**給相關人員。
* **自動維護**:排程 Job 定期掃描 `machines.last_heartbeat_at`,處理長期失聯機台。
---
## 7. 業務邏輯細節 (Fine-grained Logic)
* **指令成功判定**:採「**被動判定**」。機台收到指令後,若在其後的 B010 心跳中回報狀態正確(如:頁面碼已變更),即視為指令執行成功。
---
## 8. 待確認事項 (已結案)
目前已根據 2026-03-12 的討論完成所有關鍵決策。

View File

@ -58,7 +58,7 @@ Route::prefix('v1')->middleware(['throttle:api'])->group(function () {
Route::prefix('app')->middleware(['iot.auth', 'throttle:100,1'])->group(function () {
// 心跳與狀態 (B010, B017, B710, B220)
Route::post('machine/status/B010', [App\Http\Controllers\Api\V1\App\MachineController::class, 'heartbeat']);
Route::get('machine/reload_msg/B017', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getSlots']);
Route::post('machine/timer/B710', [App\Http\Controllers\Api\V1\App\MachineController::class, 'syncTimer']);
Route::post('machine/coins/B220', [App\Http\Controllers\Api\V1\App\MachineController::class, 'syncCoinInventory']);