# TAS MQTT 協定規格書

本文件詳細說明 TAS（電話連結服務）的 MQTT 協定規格，供 AI 生成程式碼時參考。

---

## 連線配置

### 基本設定

| 項目 | 說明 | 值 |
|------|------|-----|
| **連線方式** | MQTT over WebSocket | 推薦 |
| **連線 URL** | WebSocket 連線路徑 | `https://tasapi.cht.com.tw/ws/mqtt/v1/` |
| **協定版本** | 支援版本 | MQTT 3.1.1 或 MQTT 5 |
| **加密協定** | TLS 最低版本 | TLS v1.2 以上 |
| **訊息 QoS** | 支援的 QoS 等級 | QoS 0 (At most once) |

### 認證

| 項目 | 說明 | 值 |
|------|------|-----|
| **Username** | MQTT 連線帳號 | API 金鑰 |
| **Password** | MQTT 連線密碼 | API 金鑰（與 Username 相同） |

### 進階功能

| 功能 | 支援 | 說明 |
|------|------|------|
| **Topic Alias** | 是 | MQTT v5 功能，可減少網路傳輸量 |
| **自動重連** | 建議實作 | 提升連線穩定性 |

---

## Topic 列表

### 1. calloutResult - 取得電話通告結果

#### 基本資訊

| 項目 | 值 |
|------|-----|
| **Topic 路徑** | `phone-conn/calloutResult/${SNKey}` |
| **模式** | Subscribe (訂閱) |
| **用途** | 接收電話通告 API 執行結果的回報訊息 |
| **QoS** | 0 |

#### 訊息屬性表

| 屬性名稱 | 資料型別 | 必要 | 說明 | 範例值 |
|---------|---------|------|------|---------|
| `groupId` | string | 是 | 通告電話群組識別號 | `"0233******.1801de67-61a6-4eca-881a-4c75f37ce588"` |
| `phone` | string | 是 | 被叫電話號碼 | `"****7777"` |
| `status` | string | 是 | 電話呼叫結果狀態 | `"answered"` |
| `statusCode` | integer | 否 | SIP 狀態碼 | `200` |
| `tag` | string | 否 | 對應的 tag 值（用於多 Session 管理，僅當原始 callout 請求包含 tags 屬性時才會有此欄位） | `"user_001"` |
| `time` | string | 是 | 回應時間 (ISO 8601 UTC) | `"2026-03-24T05:26:37.623859240Z"` |

#### Status 狀態值

| 狀態值 | 說明 |
|--------|------|
| `answered` | 對方已接聽，或進入語音信箱 |
| `reject` | 行動電話拒接或忙線 |
| `busy` | 市內電話使用中 |
| `timeout` | 未接聽（超過 ringingTimeout） |
| `notfound` | 空號 |
| `failed` | 呼叫發生錯誤 |

#### 訊息範例

```json
{
  "groupId": "0233******.1801de67-61a6-4eca-881a-4c75f37ce588",
  "id": "1f2dbc3e;tw.voip.IoTIMS",
  "phone": "****7777",
  "status": "answered",
  "statusCode": 200,
  "tag": "user_001",
  "time": "2026-03-24T05:26:37.623859240Z"
}
```

---

### 2. callEvent - 取得 DTMF 按鍵及電話事件

#### 基本資訊

| 項目 | 值 |
|------|-----|
| **Topic 路徑** | `phone-conn/callEvent/${SNKey}` |
| **模式** | Subscribe (訂閱) |
| **用途** | 接收 DTMF 輸入、語音辨識結果、電話事件等 |
| **QoS** | 0 |

#### 訊息屬性表

| 屬性名稱 | 資料型別 | 必要 | 說明 | 範例值 |
|---------|---------|------|------|---------|
| `id` | string | 是 | 請求識別碼（須在 callAction 回應時使用） | `"1f2dbc3e;tw.voip.IoTIMS.ctK"` |
| `type` | string | 是 | 訊息型態 | `"calloutRequest"` |
| `phone` | string | 是 | 撥入或被叫的電話號碼 | `"****7777"` |
| `tag` | string | 否 | 對應的 tag 值（用於多 Session 管理，僅限 callout 相關訊息且原始請求包含 tags 屬性時才會有此欄位） | `"user_001"` |
| `node` | string | 否 | 目前執行的節點，可能以逗號分隔包含用戶輸入值 | `"MAIN,1"` |
| `event` | string | 否 | 電話事件類型 | `"CallEstablished"` |
| `time` | string | 是 | 訊息產生時間 (ISO 8601 UTC) | `"2026-03-24T05:26:38.533981588Z"` |
| `data` | string | 否 | 錄音檔案 (base64 編碼) | `"6KiI566X5qmf5q235Y+y..."` |
| `text` | string | 否 | 錄音檔案轉換後的文字 | `"台北"` |

#### Type 訊息型態

| 型態值 | 說明 | 備註 |
|--------|------|------|
| `calloutRequest` | 互動式 callout 請求 | 由 Callout API 觸發，需在 callAction 回應 |
| `calloutEvent` | 互動式 callout 事件 | 搭配 event 屬性，如 CallEstablished、CallHangUp |
| `request` | Call-in 服務請求 | 外部來電撥入系統號碼時觸發，需在 callAction 回應 |
| `event` | 電話狀態事件 | 搭配 event 屬性，適用於 call-in 場景 |
| `recordEvent` | 錄音檔案事件 | 包含錄音內容 |

#### Event 電話事件

| 事件值 | 說明 |
|--------|------|
| `CallEstablished` | 電話已接通，開始播放語音 |
| `CallHangUp` | 用戶已掛斷電話 |

#### Node 格式

**用戶按下 DTMF 按鍵時：**
```
"node": "ask01,1"
```
前半部分 = 節點名稱，後半部分 = 用戶輸入

**用戶語音輸入時（開啟 collectText）：**
```
"node": "queryNode,台北/0.9"
```
前半部分 = 節點名稱，後半部分 = STT 結果 / 信心值

**超時無輸入時：**
```
"node": "MAIN,timeout"
```

#### 訊息範例

**Callout 請求訊息**
```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.ctK",
  "type": "calloutRequest",
  "phone": "****7777",
  "tag": "user_001",
  "node": "MAIN",
  "time": "2026-03-24T05:26:38.533981588Z"
}
```

**電話接通事件**
```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS",
  "type": "event",
  "phone": "****7777",
  "tag": "user_001",
  "event": "CallEstablished",
  "time": "2026-03-24T05:26:37.623869240Z"
}
```

**用戶輸入 DTMF**
```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.gVT",
  "type": "calloutRequest",
  "phone": "****7777",
  "tag": "user_001",
  "node": "ask01,1",
  "time": "2026-03-24T05:26:46.776983114Z"
}
```

---

### 3. callAction - 發佈電話執行動作

#### 基本資訊

| 項目 | 值 |
|------|-----|
| **Topic 路徑** | `phone-conn/callAction/${SNKey}` |
| **模式** | Publish (發佈) |
| **用途** | 回應系統請求，控制電話流程 |
| **QoS** | 0 |

#### 核心屬性表

| 屬性名稱 | 資料型別 | 必要 | 說明 | 約束條件 |
|---------|---------|------|------|---------|
| `id` | string | 是 | 與 callEvent 中的 id 相同 | 必須匹配 |
| `node` | string | 是 | 節點名稱 | `[A-Za-z][A-Za-z0-9_]*` 最長 16 字 ，禁用 MAIN/CUSTOM/END |
| `text` | string | 否 | 要播放的文字內容 | 最長 200 字（計數規則同上） |
| `textSpeed` | number | 否 | 文字播放速度 | 0.0~10.0，F/F1 預設 0.9，F2/M 預設 1.0 |
| `nextNode` | string | 否 | 下個執行的節點 | MAIN/CUSTOM/END 或節點名稱 |
| `promptMode` | string | 否 | 語音性別 | F/F1/F2 (女聲) 或 M (男聲)，預設 M |
| `repeat` | integer | 否 | text 重複次數 | 1~99，預設 1 |
| `betweenTextRepeatDelay` | integer | 否 | text 重複間隔秒數 | 0~99，預設 1，0 表示系統預設值 |

#### 輸入收集屬性表

| 屬性名稱 | 資料型別 | 必要 | 說明 | 約束條件 |
|---------|---------|------|------|---------|
| `collectDTMF` | boolean | 否 | 開啟 DTMF 按鍵收集 | 與 collectDigits/collectText 互斥，預設 false |
| `maxDTMF` | integer | 否 | 最多收集 DTMF 數量 | 1~20，預設 1 |
| `collectDTMFTimeout` | integer | 否 | DTMF 收集超時秒數 | 10~60，預設 maxDTMF * 2 |
| `collectDigits` | boolean | 否 | 開啟數字辨識 | 與 collectDTMF/collectText 互斥，預設 false |
| `collectText` | boolean | 否 | 開啟語音辨識 | 與 collectDTMF/collectDigits 互斥，預設 false |
| `sttMode` | string | 否 | 語音辨識模式 | stream (快速) 或 record (高準確度)，預設 stream |
| `maxRecordDuration` | integer | 否 | 錄音最長秒數 | 0~999，0 表示不開啟錄音 |
| `recordToText` | boolean | 否 | 錄音是否轉文字 | 搭配 maxRecordDuration > 0 使用 |

#### 進階屬性表

| 屬性名稱 | 資料型別 | 必要 | 說明 | 約束條件 |
|---------|---------|------|------|---------|
| `playDTMF` | string | 否 | 播放 DTMF 按鍵音 | 0-9、w (暫停 0.5 秒)，最長 60 字 |
| `transferTo` | string | 否 | 轉接電話號碼 | 格式 0XXXXXXXXX |
| `ringingTimeout` | integer | 否 | 轉接電話振鈴超時秒數 | 15~120，預設 30 |
| `sttEngineOptions` | object | 否 | STT 引擎設定 | 選擇性參數，用於配合語音辨識功能使用 |
| `isWaitText` | boolean | 否 | 是否為等待訊息 | true 時用於發送等待訊息，不需設定 node，訊息後系統自動播放等候音樂 |

#### sttEngineOptions 詳細說明

`sttEngineOptions` 參數用於配合語音辨識 (STT) 功能使用，參數格式為一個 JSON 物件，目前支援以下 model 設定：

| Model 值 | 適用情境 | 說明 |
|---------|---------|------|
| `wenet_cs_general` | 一般對話 | 用於一般對話情境的語音辨識 |
| `wenet_yesno` | 簡短回覆 | 加強辨識「是」或「否」等簡短回覆語句 |
| `wenet_eddie` | 姓名辨識 | 加強姓名辨識能力，適合姓名或人名相關場景 |

**使用範例**：

```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.ctK",
  "node": "askQuestion",
  "text": "請問您的名字是什麼",
  "collectText": true,
  "sttEngineOptions": {
    "model": "wenet_eddie"
  },
  "nextNode": "CUSTOM"
}
```

**說明**：
- 此範例中使用 `wenet_eddie` model，適合用於收集用戶姓名
- `sttEngineOptions` 應搭配 `collectText: true` 使用才能啟動語音辨識功能
- 若未設定 `sttEngineOptions`，則使用系統預設值

#### NextNode 保留字

| 值 | 說明 |
|----|------|
| `MAIN` | 跳回主選單 |
| `CUSTOM` | 等待 MQTT 應用程式回應下個動作，等待超過 20 秒判定為逾時 |
| `END` | 播放後掛斷電話 |

#### 等待訊息機制

當應用程式需要長時間進行資料查詢或處理時，應使用「等待訊息」機制避免 20 秒超時造成的錯誤：

**流程說明：**
1. 收到 callEvent 請求訊息（id: abc123）
2. 立即回應「等待訊息」（id: abc123, isWaitText: true）
3. 訊息播放完畢後，系統自動播放等候音樂（最多 4 分鐘）
4. 應用程式完成處理後，發送「完成操作訊息」（id: abc123, node: final）
5. 系統停止音樂，播放完成操作訊息

**等待訊息屬性：**

| 屬性名稱 | 資料型別 | 必要 | 說明 | 約束條件 |
|---------|---------|------|------|---------||
| `id` | string | 是 | 與所要回覆的 callEvent/request 相同的識別碼 | 必須匹配 |
| `isWaitText` | boolean | 是 | 標記為等待訊息 | 必須為 true |
| `text` | string | 是 | 用戶等待時播放的訊息內容 | 最長 200 字（計數規則同 callAction） |
| `textSpeed` | number | 否 | 文字播放速度 | 0.0~10.0，F/F1 預設 0.9，F2/M 預設 1.0，不合法值被忽略 |
| `promptMode` | string | 否 | 語音性別 | F/F1/F2 (女聲) 或 M (男聲)，預設 M |
| `repeat` | integer | 否 | text 重複次數 | 1~99，預設 1 |
| `betweenTextRepeatDelay` | integer | 否 | text 重複間隔秒數 | 0~99，預設 0（系統預設值），設為 0 表示使用系統預設值 |

**超時行為：**
- 等待訊息播放完畢後，系統自動播放等候音樂
- 若 4 分鐘後仍未收到完成操作訊息，系統會自動掛斷此通電話
- 應用程式應在 4 分鐘內發送完成操作訊息

#### 訊息範例

**簡單文字播放**
```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.ctK",
  "node": "ask01",
  "text": "今日來店用餐請問覺得滿意嗎？ 滿意請按 1，覺得可以更好請按 2",
  "collectDTMF": true,
  "maxDTMF": 1,
  "collectDTMFTimeout": 15,
  "promptMode": "F",
  "nextNode": "CUSTOM"
}
```

**等待訊息（長時間處理時先回應）**
```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.ctK",
  "isWaitText": true,
  "text": "正在查詢資料，請稍候",
  "repeat": 2,
  "betweenTextRepeatDelay": 1,
  "promptMode": "F"
}
```

**完成操作訊息（處理完成後發送）**
```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.ctK",
  "node": "final",
  "text": "目前的氣溫是 25 度，PM2.5 是 20",
  "nextNode": "END"
}
```

**轉接電話**
```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.ctK",
  "node": "transfer",
  "text": "正在為您轉接",
  "transferTo": "0212345678",
  "ringingTimeout": 30,
  "nextNode": "CUSTOM"
}
```

---

### 4. callActionDebug - 取得除錯訊息

#### 基本資訊

| 項目 | 值 |
|------|-----|
| **Topic 路徑** | `phone-conn/callActionDebug/${SNKey}` |
| **模式** | Subscribe (訂閱) |
| **用途** | 接收 callAction 訊息的錯誤回報 |
| **QoS** | 0 |

#### 訊息屬性表

| 屬性名稱 | 資料型別 | 說明 |
|---------|---------|------|
| `id` | string | 對應的 callAction id |
| `errors` | array | 錯誤列表 |
| `errors[].field` | string | 有問題的屬性名稱 |
| `errors[].message` | string | 錯誤說明 |
| `time` | string | 錯誤發生時間 |

**注意**：callActionDebug 訊息不包含 tag 屬性

#### 訊息範例

```json
{
  "id": "1f2dbc3e;tw.voip.IoTIMS.ctK",
  "errors": [
    {
      "field": "node",
      "message": "node value 'INVALID_NAME_TOO_LONG_OVER_16_CHARS' exceeds maximum length of 16"
    },
    {
      "field": "text",
      "message": "text exceeds maximum length of 200 characters"
    }
  ],
  "time": "2026-03-24T05:26:40.000000000Z"
}
```

---

## 訊息流程時序

### 典型的 Callout 互動流程

```
1. 應用程式發起 Callout (REST API)
   ↓
2. 收到 calloutResult 訊息 (status: answered)
   ↓
3. 收到 callEvent 訊息 (type: calloutEvent, event: CallEstablished)
   ↓
4. 收到 callEvent 訊息 (type: calloutRequest, node: MAIN)
   ↓
5. 發佈 callAction 訊息 (第一個問題)
   ↓
6. 收到 callEvent 訊息 (用戶輸入: node: ask01,1)
   ↓
7. 發佈 callAction 訊息 (第二個問題)
   ↓
8. 收到 callEvent 訊息 (用戶輸入: node: ask02,2)
   ↓
9. 發佈 callAction 訊息 (結束，nextNode: END)
   ↓
10. 收到 callEvent 訊息 (type: event, event: CallHangUp)
```

---

## 多 Session 管理

### 使用 tag 進行 Session 識別

當應用程式需要同時處理多個 callout（超過 20 筆號碼時分批發送），建議使用 tags 屬性進行 Session 管理：

**發起 Callout 時設置 tags**
```json
{
  "serviceNumber": "your_service_number",
  "phones": ["021234****", "098765****", "091234****"],
  "tags": ["user_001", "user_002", "user_003"]
}
```

**MQTT 訊息中的 tag**
僅當原始 RESTful callout 請求中有 tags 屬性時，callEvent 和 calloutResult 訊息才會包含對應的 tag 屬性。call-in 相關訊息則不包含 tag。callActionDebug 訊息也不包含 tag 屬性。
```json
{
  "id": "...",
  "type": "calloutRequest",
  "phone": "****5577",
  "tag": "user_001",
  "node": "MAIN",
  "time": "..."
}
```

**Session 管理**
```
sessionMap = {
  "user_001": { phone, status, answers, ... },
  "user_002": { phone, status, answers, ... },
  "user_003": { phone, status, answers, ... }
}

// 收到 callEvent 或 calloutResult 訊息時（含 tag 屬性）
if (message.tag) {
  session = sessionMap[message.tag]
  // 更新 session 狀態和數據
}
```

---

## 重要注意事項

1. **20 秒超時提示**：系統約 20 秒沒收到回應會播放音樂，務必在此時限內發送 callAction
   - 若需長時間處理，應先發送等待訊息（isWaitText: true）
   - 等待訊息播放後系統自動播放等候音樂（最多 4 分鐘）
   - 處理完成後發送完成操作訊息（相同 id，node: final）

2. **訊息配對**：callAction（包括等待訊息和完成操作訊息）中的 `id` 必須與接收到的 callEvent/request 中的 `id` 完全相同
   - 等待訊息和完成操作訊息使用同一個 id
   - 系統透過 id 匹配來確認訊息對應關係

3. **互斥功能**：collectDTMF、collectDigits、collectText 三項不可同時開啟

4. **isWaitText 特殊處理**：
   - 若 isWaitText 為 true，則 node、nextNode、以及所有輸入收集屬性無作用
   - 等待訊息只需要設定：id、isWaitText、text、textSpeed、promptMode、repeat、betweenTextRepeatDelay

5. **轉接計費**：使用 transferTo 功能會被視為額外的 callout 電話計費

---

## 參考資源

- REST API 文件：https://tasapi.cht.com.tw/yaml/download/tas.yaml
- 完整 MQTT 文件：https://tasapi.cht.com.tw/tas/api/tas_document_mqtt
- 資源下載：https://tasapi.cht.com.tw/tas/api/tas_resource
