diff --git a/.agents/rules/api-rules.md b/.agents/rules/api-rules.md index c3d9ffe..4a88290 100644 --- a/.agents/rules/api-rules.md +++ b/.agents/rules/api-rules.md @@ -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 ` 進行身份驗證。 * **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 ` 識別。針對 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`。 diff --git a/.agents/skills/api-technical-specs/SKILL.md b/.agents/skills/api-technical-specs/SKILL.md index 6af4e55..6257d25 100644 --- a/.agents/skills/api-technical-specs/SKILL.md +++ b/.agents/skills/api-technical-specs/SKILL.md @@ -21,7 +21,7 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與 - **Header**:`Authorization: Bearer `。 ### 2.2 機台通訊認證 (Machine Authentication) -- **適用 API**:B010, B012, B013, B600 等後續通訊。 +- **適用 API**:B012, B013, B600 等後續通訊。 - **方式**:使用機台專屬之 **api_token**。 - **Header**:`Authorization: Bearer `。 @@ -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 端應依據呼叫的方法決定數據處理方式。 diff --git a/.agents/skills/mqtt-communication-specs/SKILL.md b/.agents/skills/mqtt-communication-specs/SKILL.md index c4d4b92..7e75a73 100644 --- a/.agents/skills/mqtt-communication-specs/SKILL.md +++ b/.agents/skills/mqtt-communication-specs/SKILL.md @@ -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 訊息中。 --- diff --git a/README.md b/README.md index 82438de..82e24cf 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Star Cloud 是一個專為智能販賣機設計的後台管理系統,負責管 ### 2. Machine IoT API (`/api/app/...`) - **對象**: 智能販賣機、計時器等硬體。 - **認證**: Header `Authorization: Bearer `。 -- **高併發處理**: 核心日誌 (B010 心跳、B600 交易) **嚴禁直寫 DB**,必須進入 **Redis Queue** 背景異步處理。 +- **高併發處理**: 核心日誌 (MQTT 心跳、B600 交易) **嚴禁直寫 DB**,必須進入 **Redis Queue** 背景異步處理。 ### 3. MQTT IoT 通訊 - **對象**: 高頻/即時性機台通訊 (心跳、異常、交易)。 diff --git a/app/Console/Commands/ListenMqttQueue.php b/app/Console/Commands/ListenMqttQueue.php index 821ba1f..30f8a91 100644 --- a/app/Console/Commands/ListenMqttQueue.php +++ b/app/Console/Commands/ListenMqttQueue.php @@ -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]; diff --git a/app/Http/Controllers/Api/V1/App/MachineController.php b/app/Http/Controllers/Api/V1/App/MachineController.php index 14732a1..eb0e62d 100644 --- a/app/Http/Controllers/Api/V1/App/MachineController.php +++ b/app/Http/Controllers/Api/V1/App/MachineController.php @@ -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) diff --git a/docs/api/iot-spec.md b/docs/api/iot-spec.md index 3c3088e..aff8e22 100644 --- a/docs/api/iot-spec.md +++ b/docs/api/iot-spec.md @@ -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」。 + diff --git a/docs/b010_technical_spec.md b/docs/b010_technical_spec.md deleted file mode 100644 index c3efd47..0000000 --- a/docs/b010_technical_spec.md +++ /dev/null @@ -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 ` 進行身份驗證。 -* **實裝方式**: - * 後台管理介面新增「核發機台 Token」功能。 - * 建立專屬 `IotAuthMiddleware`,驗證請求中的 Token 與 `machines` 表是否匹配。 - ---- - -## 6. 離線與告警機制 (Heartbeat Logic) - -* **在線判定**:心跳超時超過 **30 秒** 即在 UI 將機台顯示為「離線」。 -* **告警機制**:機台斷線時,系統需即時產出**警示或推播通知**給相關人員。 -* **自動維護**:排程 Job 定期掃描 `machines.last_heartbeat_at`,處理長期失聯機台。 - ---- - -## 7. 業務邏輯細節 (Fine-grained Logic) - -* **指令成功判定**:採「**被動判定**」。機台收到指令後,若在其後的 B010 心跳中回報狀態正確(如:頁面碼已變更),即視為指令執行成功。 - ---- - -## 8. 待確認事項 (已結案) - -目前已根據 2026-03-12 的討論完成所有關鍵決策。 diff --git a/routes/api.php b/routes/api.php index 1b26747..c110905 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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']);