[FIX] 修復遠端 OTA 更新失敗並依環境區分 APK 版本後綴
1. 新增 DOWNLOAD_WITHOUT_NOTIFICATION 權限:OtaManager 改用 DownloadManager 後以 VISIBILITY_HIDDEN 靜默下載,缺此權限會在 enqueue 時丟出「Invalid value for visibility: 2」導致每次遠端更新一啟動就失敗。 2. versionName 後綴依 build type 動態切換:release → _R(正式)、debug → _D(測試),現場可一眼辨識 APK 來源;versionCode 不含後綴、兩者相同不影響 OTA 比版。 3. compiler-apk workflow 的後台 keyin 列印由寫死 _R 改為 _D,與實際 debug 檔名一致。 4. verPatch 升至 13。 5. 新增 docs/network-signal-reporting-spec.md 網路訊號上報規格文件。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c7da4d1a12
commit
8d764ba388
@ -105,7 +105,7 @@ if [ -n "$APK_SOURCE" ]; then
|
||||
|
||||
MINOR=$(grep "def verMinor" app/build.gradle | awk '{print $4}')
|
||||
PATCH=$(grep "def verPatch" app/build.gradle | awk '{print $4}')
|
||||
VERSION_NAME="${MAJOR}_0${MINOR}_${PATCH}_R"
|
||||
VERSION_NAME="${MAJOR}_0${MINOR}_${PATCH}_D"
|
||||
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH))
|
||||
|
||||
echo "=================================================="
|
||||
|
||||
@ -10,7 +10,7 @@ plugins {
|
||||
// 避免不同尺寸機台間版本號產生衝突。
|
||||
// =========================================================================
|
||||
def verMinor = 2
|
||||
def verPatch = 11
|
||||
def verPatch = 13
|
||||
|
||||
|
||||
|
||||
@ -76,9 +76,12 @@ android {
|
||||
verMajor = 10 // 10 吋
|
||||
}
|
||||
|
||||
// 環境後綴:release → R(正式)、debug → D(測試),供現場一眼辨識 APK 來源
|
||||
def envSuffix = (buildType == "release") ? "R" : "D"
|
||||
|
||||
// 動態重算並覆寫此 Variant 的版本號,避免互相污染
|
||||
def calculatedVersionCode = verMajor * 10000 + calculatedMinor * 100 + calculatedPatch
|
||||
def calculatedVersionName = "${verMajor}_0${calculatedMinor}_${calculatedPatch}_R"
|
||||
def calculatedVersionName = "${verMajor}_0${calculatedMinor}_${calculatedPatch}_${envSuffix}"
|
||||
|
||||
|
||||
variant.outputs.each { output ->
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<!-- OTA 以 DownloadManager VISIBILITY_HIDDEN 靜默下載,需此權限否則 enqueue 會丟 Invalid value for visibility: 2 -->
|
||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||
|
||||
<application
|
||||
android:name=".MyApp"
|
||||
|
||||
185
docs/network-signal-reporting-spec.md
Normal file
185
docs/network-signal-reporting-spec.md
Normal file
@ -0,0 +1,185 @@
|
||||
# 機台網路訊號上報規格(RSRP / RSSI / SINR)
|
||||
|
||||
> 目的:讓後台(star-cloud)能讀取現場機台的行動網路品質,用於監控與低訊號告警。
|
||||
> 狀態:規格定案,待實作。最後更新:2026-06-18。
|
||||
> 設計原則:**全程 additive**,沿用既有 heartbeat / machine_logs 架構,不破壞線上 main(晟崴/中國醫)既有 API 與資料結構。
|
||||
|
||||
---
|
||||
|
||||
## 0. 全鏈路(沿用現有路徑,無新通道)
|
||||
|
||||
```
|
||||
App (每 60s heartbeat)
|
||||
→ MQTT machine/{serial}/heartbeat ← payload 加巢狀 network 物件
|
||||
→ Go Gateway RPUSH redis: mqtt_incoming_jobs
|
||||
→ Laravel php artisan mqtt:listen (BLPOP)
|
||||
→ Job ProcessHeartbeat.php ← 解析 network + 寫入
|
||||
→ DB machines(快照欄位) + machine_logs(時序,仿溫度)
|
||||
→ Admin index.blade.php network 分頁(仿 temperatureAjax 圖表)
|
||||
```
|
||||
|
||||
已驗證事實(原始碼對帳):
|
||||
- App:`MqttManager` 每 60s 發 `machine/{serial}/heartbeat`;payload 為 Gson 序列化的 `HeartbeatPayload`(snake_case,目前僅 `firmware_version`/`temperature`)。
|
||||
- App:`FontendActivity.getMobileSignalStrength()` 目前只呼叫 `CellSignalStrengthLte.getDbm()`,無 5G、無 RSSI/SINR。
|
||||
- App:`app/build.gradle` defaultConfig `minSdk 21`、`targetSdk 34`、`compileSdk 35`;**注意 flavor S9XYU 的 `minSdk 28`**,S12XYU 繼承 21。兩者皆低於訊號 getter 所需 API。
|
||||
- 後台:`mqtt-gateway/` Go → `ListenMqttQueue.php`(BLPOP) → `ProcessHeartbeat::dispatch`,`type=heartbeat` 路由確認。
|
||||
- 後台:`ProcessHeartbeat.php` 目前更新 `last_heartbeat_at/status/temperature/firmware_version`。
|
||||
- 後台:machines 表「無」任何訊號欄位。
|
||||
- 後台:**溫度曲線無專用時序表**,是讀 `machine_logs` 用 `message='Temperature reported: :temp°C'` + `context['temp']` 過濾(`MachineController.php` `temperatureAjax()`),且 `ProcessHeartbeat` 只在溫度變化時才寫 log → 內建降頻。
|
||||
|
||||
---
|
||||
|
||||
## 1. App 端
|
||||
|
||||
### 1.1 HeartbeatPayload 擴充
|
||||
在 `HeartbeatPayload`(`structure/mqtt/HeartbeatPayload.java`)加一個**巢狀** `network` 物件(不要平鋪,確保舊解析零影響):
|
||||
|
||||
```json
|
||||
{
|
||||
"firmware_version": "1.2.3",
|
||||
"temperature": 25,
|
||||
"network": {
|
||||
"type": "cellular", // cellular | wifi | ethernet
|
||||
"tech": "LTE", // LTE | NR | null
|
||||
"rsrp": -95, // dBm,行動網路主要覆蓋指標
|
||||
"rssi": -65, // dBm
|
||||
"sinr": 12, // dB
|
||||
"rsrq": -10 // dB
|
||||
}
|
||||
}
|
||||
```
|
||||
- WiFi / 有線:`type` 對應,行動欄位給 null。
|
||||
- 機台未送 `network`(舊版)→ 後台視為 null,不寫訊號 log。
|
||||
|
||||
### 1.2 訊號讀取(抽成 util,與 UI icon 共用)
|
||||
把 `getMobileSignalStrength()` 邏輯抽成可重用方法,補齊三項並加防護:
|
||||
|
||||
```java
|
||||
// LTE:getRsrp/getRssi 需 API 29+;getRssnr(=SINR) 需 API 32+
|
||||
CellSignalStrengthLte lte = ((CellInfoLte) cellInfo).getCellSignalStrength();
|
||||
int rsrp = (SDK_INT >= 29) ? lte.getRsrp() : lte.getDbm();
|
||||
int rssi = (SDK_INT >= 29) ? lte.getRssi() : Integer.MAX_VALUE;
|
||||
int sinr = (SDK_INT >= 32) ? lte.getRssnr() : Integer.MAX_VALUE;
|
||||
int rsrq = (SDK_INT >= 29) ? lte.getRsrq() : Integer.MAX_VALUE;
|
||||
|
||||
// 5G NR:需判斷 CellInfoNr / CellSignalStrengthNr(API 31+)
|
||||
// getSsRsrp() / getSsRsrq() / getSsSinr(),tech 標記為 "NR"
|
||||
```
|
||||
|
||||
**必做防護(否則低版機台崩潰 / 5G 機台誤報無訊號):**
|
||||
1. 所有 API 29+/32+ getter 一律包 `Build.VERSION.SDK_INT` 判斷;取不到的值以 null 上報(`Integer.MAX_VALUE` 視為 null)。
|
||||
2. **版本防護用 inline guard 即可(agy 詰問後共識,原判已撤回)**:
|
||||
- 本專案 minSdk 21 = ART,採惰性驗證;對「**既有類別的新方法**」(如 `CellSignalStrengthLte.getRsrp()`,類別自 API 17 即存在)只要包 `Build.VERSION.SDK_INT` inline guard 就安全,**不會** class-load `VerifyError`/啟動崩潰。這也是本檔既有慣例([FontendActivity.java:709](file:///home/mama/projects/TaiwanStar_general_size_s_Chengwai/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java#L709) 的 `getSignalStrength()` API29、:1474 的 TIRAMISU API33 皆 inline 出貨)。Wrapper/Helper class 只是可選風格,非正確性必需。
|
||||
- **唯一例外**:引用 API 29+/30+ 才有的**新類別「型別」**(`CellInfoNr`/`CellSignalStrengthNr`)時,少數客製 ROM 仍可能 `NoClassDefFoundError`。故 **5G 分支整段務必在 `SDK_INT` guard 內**(見下點)。
|
||||
3. **5G NSA fallback(agy 查核)**:現場 5G 多為 **NSA(非獨立組網)**,控制面錨定在 LTE,`getAllCellInfo()` 主小區仍是 `CellInfoLte`。所以**不能只靠 `CellInfoNr`**;邏輯應為「先取 `CellInfoNr`(SA 5G,整段包在 API 31+ guard 內),取不到則 fallback `CellInfoLte`」,否則 NSA 機台拿不到訊號。
|
||||
4. **權限:真機需驗證 + 穩健性缺口(agy 詰問後降級,非確證致命 bug)**:`checkPermissionByAccessFineLocation()` 在 Android M+ 直接 `return true`,**沒做執行期動態申請**,且全專案無 `requestPermissions`、非平台簽章。但 `getMobileSignalStrength()` 有 `try-catch(SecurityException)` → 即使未授權也只是回 -1,**不會崩**。
|
||||
- 這些是**專用 kiosk 機**,`ACCESS_FINE_LOCATION` 多半在佈署時用 `adb shell pm grant` 或 provisioning 預授;且 cellular 分支只在 active transport==CELLULAR 時才跑(現場多走 WiFi/有線),故是否真壞**未經現場驗證**。
|
||||
- **行動**:實作訊號上報前,先在目標機台確認權限已授(`adb shell dumpsys package <pkg> | grep ACCESS_FINE_LOCATION`);若未授,選擇「佈署腳本 `pm grant`」或「App 內補執行期申請」其一即可。不列為阻斷性 bug,但**不確認就上線,行動網路訊號會全空**。
|
||||
|
||||
### 1.3 commit 注意
|
||||
本分支 `feat/general-cart-and-payment` 正在改 `MqttManager`;`HeartbeatPayload` 擴充與訊號讀取請**獨立小 commit**,避免與購物車/支付改動纏在一起。
|
||||
|
||||
---
|
||||
|
||||
## 2. 後台 star-cloud
|
||||
|
||||
### 2.1 採用既有慣例:寫 machine_logs,不另建表
|
||||
初版**不新建 `machine_network_logs` 表**,沿用溫度同一條管道(已驗證可行):
|
||||
|
||||
`ProcessHeartbeat::handle()` 在解析 `network` 後(整段包在 `if (isset($payload['network']))`,舊機台不進):
|
||||
|
||||
1. **更新 machines 快照欄位**(列表頁即時格數用)。**防 null 覆蓋(agy 查核)**:僅在 `network.type === 'cellular'` 時才寫 `last_rsrp/last_rssi/last_sinr`;切到 WiFi/有線時行動欄位為 null,若無條件覆蓋會把先前的 LTE 快照洗掉。
|
||||
2. **寫 machine_logs 時序**,但**降頻**(見 §3 閾值):
|
||||
- type = `network`
|
||||
- message = `'Signal reported'`
|
||||
- context = `{ "rsrp": -95, "rssi": -65, "sinr": 12, "type": "cellular", "tech": "LTE" }`
|
||||
3. **低訊號告警**(見 §3):達門檻時另寫一筆 `level=warning`/`error` 的 log,複用 `MachineLog` 既有 Discord hook。
|
||||
|
||||
> 若日後需密集採樣再升級為獨立 `machine_network_logs` 表(仿 `machine_logs` append-only、`const UPDATED_AT = null`、複合索引 `(machine_id, recorded_at)`)。初版不需要。
|
||||
|
||||
### 2.2 machines 主表新增 nullable 快照欄位
|
||||
新 migration(仿 `add_status_to_machines_table`),全部 nullable(additive、不破壞既有 query):
|
||||
|
||||
```php
|
||||
$table->string('network_type')->nullable(); // cellular/wifi/ethernet
|
||||
$table->smallInteger('last_rsrp')->nullable();
|
||||
$table->smallInteger('last_rssi')->nullable();
|
||||
$table->smallInteger('last_sinr')->nullable();
|
||||
$table->timestamp('last_network_at')->nullable();
|
||||
```
|
||||
同步加入 `Machine` model `$fillable`。
|
||||
|
||||
### 2.3 Admin 介面(仿溫度圖)
|
||||
- 新增 `signalAjax()`,複製 `temperatureAjax()` 改成過濾 `message='Signal reported'`、輸出 `context['rsrp']`(及 sinr)成時間序列。
|
||||
- 前端 `admin/machines/index.blade.php`:
|
||||
- 列表頁狀態欄旁加「訊號」格,用 `last_rsrp` 換算格數(與 App icon 邏輯一致)。
|
||||
- 詳情面板新增 `network` 分頁,複製溫度圖 Alpine.js 邏輯,打 `signalAjax` 畫 RSRP / SINR 折線。
|
||||
|
||||
---
|
||||
|
||||
## 3. 閾值定義(本規格核心數值)
|
||||
|
||||
### 3.1 指標對照(業界通用)
|
||||
| 指標 | 單位 | 優 | 良 | 中 | 差 | 極差 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **RSRP**(覆蓋,主指標) | dBm | ≥ -80 | -80~-90 | -90~-100 | -100~-110 | < -110 |
|
||||
| **RSSI**(總功率,參考) | dBm | ≥ -65 | -65~-75 | -75~-85 | -85~-95 | < -95 |
|
||||
| **SINR**(訊噪比,越大越好) | dB | ≥ 20 | 13~20 | 0~13 | < 0 | — |
|
||||
|
||||
販賣機只做小流量(心跳/交易/同步),重點是**穩定不斷線**,非高速。**-110 dBm 為危險線,-100 以上視為健康。**
|
||||
|
||||
### 3.2 寫 log 降頻閾值
|
||||
- **RSRP 變化 ≥ 5 dB** 才寫一筆(RSRP 自然抖動約 ±3 dB,故用 5 dB 去噪)。
|
||||
- **保底**:即使無明顯變化,**最多每 30 分鐘補寫一筆**(曲線不斷、可確認機台仍在線且穩定)。
|
||||
- 判斷依據:比對 `machines.last_rsrp` 與 `machines.last_network_at`。
|
||||
|
||||
### 3.3 低訊號告警閾值(Discord)
|
||||
- **RSRP < -110 dBm 且連續 3 次心跳(約 3 分鐘)皆低於** → 告警(避免瞬間抖動誤報)。
|
||||
- 輔助:**SINR < 0 dB 持續** → 一併告警(干擾大、易斷)。
|
||||
- **加 RSRQ(agy 建議)**:RSRP 看似正常(如 -95)但 **RSRQ < -15 dB** 代表多重路徑反射/同頻干擾嚴重,仍會交易超時 → RSRQ < -15 dB 持續也告警。
|
||||
- 分級建議:
|
||||
- RSRP -100 ~ -110:`warning`(觀察)
|
||||
- RSRP < -110:`error`(派人檢查天線/機台位置)
|
||||
|
||||
### 3.4 ⚠️ 告警盲點與修正(agy 查核,最致命)
|
||||
**問題**:RSRP < -110 時機台往往已斷網,根本**發不出**那筆低訊號 heartbeat,後台「連續 3 次 < -110」的條件永遠湊不齊 → 訊號最差的機台反而不會告警。
|
||||
|
||||
**修正**:低訊號告警**必須與既有「離線偵測機制」結合**(比對 `last_heartbeat_at` 逾時的排程)。判定離線時主動檢查該機台 `machines.last_rsrp`:
|
||||
- 若最後一筆 RSRP 已 < -105 dBm → 告警訊息註記「機台離線,最後上報訊號極差 (RSRP: -108dBm),疑似訊號不佳斷線」。
|
||||
- 否則照原離線告警處理。
|
||||
|
||||
如此「訊號差→斷線→離線告警帶出最後訊號」與「訊號差但仍在線→低訊號告警」兩條路徑互補,涵蓋完整。
|
||||
|
||||
---
|
||||
|
||||
## 4. 相容性檢查(對照後台慣例)
|
||||
- ✅ heartbeat payload 用巢狀 `network`,舊欄位零變動;舊機台不送 → null。
|
||||
- ✅ machines 全用 nullable 欄位,既有 query/API 回應結構不破壞。
|
||||
- ✅ 沿用 machine_logs + 新增 admin endpoint,不碰 B 系列既有 endpoint。
|
||||
- ⚠️ 註:「nullable/不破壞回應結構/B 系列不可刪」是後台**實務慣例**(migration/fillable 習慣),非 `api-rules.md` 明文 MUST 條款;仍應遵循。
|
||||
|
||||
---
|
||||
|
||||
## 5. 實作清單(給後台同事 / 後續排程)
|
||||
**App 端**
|
||||
- [ ] **先驗權限**:在目標機台確認 `ACCESS_FINE_LOCATION` 已授(`adb shell dumpsys package <pkg> | grep FINE_LOCATION`);未授則 `pm grant` 或補執行期申請(§1.2-4,非阻斷但不確認會全空)
|
||||
- [ ] 訊號讀取:補 RSSI/SINR/RSRQ + inline `Build.VERSION.SDK_INT` 防護(符合本庫慣例,毋須 Wrapper class)+ `CellInfoNr`→`CellInfoLte` fallback、5G 分支整段在 API31+ guard 內(§1.2-2/3)
|
||||
- [ ] `HeartbeatPayload` 加巢狀 `network`(含對應 class,測 Gson JSON 結構;null 不序列化已驗證安全)
|
||||
- [ ] 獨立 commit(勿混入購物車/支付)
|
||||
|
||||
**後台 star-cloud**
|
||||
- [ ] migration:machines 加 5 個 nullable 快照欄位 + 更新 `Machine` fillable
|
||||
- [ ] `ProcessHeartbeat`:解析 `network` → **僅 cellular 才更新快照(防 null 覆蓋)** + 依 §3.2 降頻寫 machine_logs + 依 §3.3 告警
|
||||
- [ ] **離線偵測排程**:判離線時帶出 `last_rsrp` 註記「疑似訊號不佳斷線」(§3.4 告警盲點修正)
|
||||
- [ ] `signalAjax()` endpoint(仿 `temperatureAjax`)
|
||||
- [ ] `index.blade.php`:列表訊號格 + 詳情 network 圖表分頁
|
||||
|
||||
---
|
||||
|
||||
## 6. 查核紀錄
|
||||
- 2026-06-18 初稿:內建 Agent 子代理對帳 App + star-cloud 原始碼,A1–A7 事實成立。
|
||||
- 2026-06-18 agy 交叉比對(Antigravity 獨立讀兩 repo):A1–A7 全部複核成立;提出 5 項補充——告警斷網盲點(§3.4)、權限缺口(§1.2-4)、VerifyError(§1.2-2)、5G NSA fallback(§1.2-3)、WiFi null 覆蓋保護(§2.1)、RSRQ 干擾告警(§3.3);更正 minSdk 因 flavor 而異(S9XYU=28)。
|
||||
- 2026-06-18 與 agy **交叉詰問**(非照單全收,回原始碼查證後辯論):
|
||||
- 爭點1「權限 🔴 致命」→ **agy 修正、降級**:有 try-catch 不會崩、kiosk 多半預授、cellular 分支少觸發 → 改列「需現場驗證的穩健性缺口」(§1.2-4)。
|
||||
- 爭點2「必須 Wrapper class 否則啟動崩」→ **agy 認錯、撤回**:minSdk21=ART 惰性驗證,inline `SDK_INT` guard 足夠且為本庫慣例(:709);唯引用 5G 新「型別」需整段 guard(§1.2-2)。
|
||||
- 5G NSA fallback / WiFi null 覆蓋 / 告警綁離線偵測三點,我覆核後同意,維持。
|
||||
Loading…
Reference in New Issue
Block a user