releaseV1.0.47 修复蓝牙ota有时不受 cat1 版本控制的问题 3429/1 3429/head 3435/target/1
author孙春阳 <snickersun@163.com>
Thu, 28 May 2026 10:09:32 +0000 (18:09 +0800)
committer孙春阳 <snickersun@163.com>
Thu, 28 May 2026 10:09:32 +0000 (18:09 +0800)
autobuild.sh
code/sdk/application/wearable/ln/nativeapp/nativeui/ota/src/OtaModel.cpp
code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp
code/sdk/zhihui/4g/cat1Service/Service/cat1_server.c
code/sdk/zhihui/4g/cat1Service/Service/cat1_tcpServer.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_api_ota.c
code/sdk/zhihui/leiniao/ln_api_cpp/ln_api_interface.cpp
code/sdk/zhihui/leiniao/ln_api_cpp/ln_api_interface.h

index b4d2e67..089cd3d 100644 (file)
@@ -59,7 +59,7 @@ declare -A PROJECT_VERSIONS=(
     ["w9"]="1.3.107" 
     ["lt22"]="1.3.107"
     ["n9"]="1.3.113"
-    ["ln"]="1.1.46"
+    ["ln"]="1.0.47"
     ["lnppp"]="1.1.1"
     ["n9ln"]="1.0.0" 
     
index c1b927f..2ce22c5 100644 (file)
@@ -295,6 +295,7 @@ namespace OHOS
     /* 4G/Cat1 模组升级:与文件下载成功/失败同一套成功页(无重启提示),可按键返回 */
     if (GetOtaStatus() == OTA_UI_STATUS_CAT1_START) {
         if (status == 3) {
+            ln_cat1_ota_set_last_failed(1);
             if (OtaView::GetInstance()) {
                 OtaView::GetInstance()->ChangeView(OtaView::OTA_VIEW_UPDATING, OtaView::OTA_VIEW_UPDATE_SUCCESS);
             }
@@ -310,13 +311,11 @@ namespace OHOS
             uint8_t min = 0;
             const bool still_1047 = (uapi_get_fw_version(&maj, &mid, &min) == ERRCODE_SUCC && maj == 1 && mid == 0 &&
                 min == 47);
-            const bool py34_prereq = ln_cat1_py34_prereq_met();
-            if (still_1047 && !py34_prereq) {
-                char kv_mark[8] = {0};
-                (void)UtilsGetValue("cat1_py34_upgrade_done", kv_mark, sizeof(kv_mark));
+            /* Cat1 会话成功但 modem+py 未达蓝牙准入:继续 Cat1,不进入手表升级 */
+            if (still_1047 && !ln_cat1_may_proceed_to_watch_ota()) {
                 WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
-                    "[ota]CheckOtaResult: Cat1 round done but py34 prereq not met, py=%u kv=%s verified=%d, stay updating",
-                    (unsigned)uapi_get_py_ver(), kv_mark, uapi_cat1_ota_is_py34_verified());
+                    "[ota]CheckOtaResult: Cat1 ok but profile not ready for watch OTA, py=%u profile=%d",
+                    (unsigned)uapi_get_py_ver(), ln_cat1_already_at_v34_source_profile());
                 SetOtaStatus(OTA_UI_STATUS_CAT1_START);
                 ThunderBirdModel::GetInstance()->PrepareCat1NextRoundAfterSuccess();
                 if (OtaUpdatingView::GetInstance()) {
@@ -332,14 +331,10 @@ namespace OHOS
                 OtaSuccessView::GetInstance()->UpdateDownloadSuccess("模组升级成功");
             }
 #ifdef SUPPORT_CAT1_SERVICE
-            /* 本次 Cat1 真升:URL 目标 V34 + 当前 py>=34 + OTA 前基线 < 34 */
-            if (status == 4 && ln_cat1_last_package_urls_target_py() == 34 && uapi_get_py_ver() >= 34 &&
-                uapi_cat1_ota_get_py_baseline() < 34) {
+            if (ln_cat1_already_at_v34_source_profile()) {
                 (void)UtilsSetValue("cat1_py34_upgrade_done", "1");
-                WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
-                    "[ota]CheckOtaResult: Cat1 verified py34, baseline=%u->%u, KV cat1_py34_upgrade_done=1",
-                    (unsigned)uapi_cat1_ota_get_py_baseline(), (unsigned)uapi_get_py_ver());
             }
+            ln_cat1_ota_set_last_failed(0);
 #endif
         }
         SetOtaStatus(OTA_UI_STATUS_NOT_START);
index 9bb2601..7e5da7c 100644 (file)
@@ -810,7 +810,11 @@ static bool IsSoftwareVersion_1_0_47(void)
      /*************************OTA升级*************************/
      int ThunderBirdModel::ota_update_boot()
      {
-          do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK, nullptr, 0);//获取是否需要OTA升级
+         if (ln_cat1_may_proceed_to_watch_ota()) {
+             ln_ota_request_watch_ota_detection("ota_update_boot");
+         } else {
+             do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_IS_CAT1_OTA_UPGRADE, nullptr, 0);
+         }
          return -1;
      }
 
index 10a40f9..c6bff10 100644 (file)
@@ -1346,8 +1346,13 @@ int cat1_handle_networ_msg(char *msg)
     if(cat1_model_oat_suc_flag == 1) //验证cat1模组有没有升级成功,如果收到消息,说明升级成功
     {
         cat1_model_oat_suc_flag = 0;//升级成功后,将标志位设为0
-        printf("模组升级成功\n");
-        push_ota_status(OTA_UI_STATUS_SUCCESS);
+        if (ln_cat1_ota_timer_check_success()) {
+            printf("模组升级成功(profile/py ok)\n");
+            ln_cat1_ota_set_last_failed(0);
+            push_ota_status(OTA_UI_STATUS_SUCCESS);
+        } else {
+            printf("模组UDP成功消息但 profile/py 未达标,继续等待\r\n");
+        }
         // LOS_Msleep(1000);
         /* 不在 4G 模组 OTA 成功后立即重启,便于继续走后续手表 OTA(原 shutdown_reboot_prejobs(2)) */
         printf("[OTA] Cat1 modem OTA success: skip immediate reboot, continue host OTA flow\r\n");
index 7a79b4a..1f64b9e 100644 (file)
@@ -1388,12 +1388,7 @@ static void  modem_ota_timer_CallBack(int data_tmp)
     printf("modem_ota_timer_CallBack %d old_model_ver %d g_py_ver %d baseline %u\n", mode_ota_count,
         old_model_ver, uapi_get_py_ver(), (unsigned)uapi_cat1_ota_get_py_baseline());
 #ifdef BOARD_LEINIAO
-    {
-        uint8_t cur_py = uapi_get_py_ver();
-        uint8_t target_py = cat1_ota_resolve_target_py();
-        uint8_t baseline_py = uapi_cat1_ota_get_py_baseline();
-        cat1_ota_ok = (cur_py >= target_py && cur_py > baseline_py) ? 1 : 0;
-    }
+    cat1_ota_ok = ln_cat1_ota_timer_check_success();
 #else
     cat1_ota_ok = (old_model_ver != uapi_get_py_ver()) ? 1 : 0;
 #endif
@@ -1401,10 +1396,11 @@ static void  modem_ota_timer_CallBack(int data_tmp)
         uint8_t TRANSMIT_DATA=TRANS_FREE;
 
         #ifdef BOARD_LEINIAO
+        ln_cat1_ota_set_last_failed(0);
         uapi_cat1_ota_mark_py34_verified();
         push_ota_status(OTA_UI_STATUS_SUCCESS);
         /* 不在 4G 模组 OTA 成功后立即重启,便于继续后续手表 OTA(原 shutdown_reboot_prejobs(REBOOT_TYPE)) */
-        printf("[OTA] Cat1 OTA ok: cur>baseline and >= target, skip immediate reboot\r\n");
+        printf("[OTA] Cat1 OTA ok: profile or py progress, skip immediate reboot\r\n");
         #else
         CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1);
         #endif
@@ -1417,15 +1413,17 @@ static void  modem_ota_timer_CallBack(int data_tmp)
 
     } else {
         mode_ota_count++;
-        if(mode_ota_count>120 || g_py_ver_new==0)
+        /* 仅超时判失败;BOOTUP 清 g_py_ver_new 不再误触发失败 */
+        if (mode_ota_count > 120)
         {
             uint8_t TRANSMIT_DATA=OTA_UPDATA_FAIL;
             CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1);
 #ifdef BOARD_LEINIAO
+            ln_cat1_ota_set_last_failed(1);
             push_ota_status(OTA_UI_STATUS_FAILED);
-            printf("[OTA] Cat1 OTA timeout/fail py=%u baseline=%u target=%u\r\n",
+            printf("[OTA] Cat1 OTA timeout fail py=%u baseline=%u target=%u profile=%d\r\n",
                 (unsigned)uapi_get_py_ver(), (unsigned)uapi_cat1_ota_get_py_baseline(),
-                (unsigned)cat1_ota_resolve_target_py());
+                (unsigned)cat1_ota_resolve_target_py(), ln_cat1_already_at_v34_source_profile());
 #endif
 
             if(modem_ota_timer){
@@ -1492,6 +1490,7 @@ void star_http_ota_updata(char*buf_1,char* buf_2,uint8_t ver)
     CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1);
     #endif
 
+    ln_cat1_ota_set_last_failed(0);
     uapi_cat1_ota_begin(ver);
     old_model_ver=ver;
 
index c81523d..31bfe7d 100644 (file)
@@ -28,7 +28,7 @@ typedef struct
 
 /* ============================== 公共宏与URL路径 ============================== */
 #define LN_VOICE_CHAT_ENABLED 0  //声纹锁开启宏
-#define LN_ENV_PRODUCTION 1  //正式环境
+#define LN_ENV_PRODUCTION 0  //正式环境
 /* 接口基础配置 - 如需切换环境/密钥,在此调整 */
 #if LN_ENV_PRODUCTION
 #define LN_BASE_URL "https://ambybox-o.api.leiniao.com/ambybox-device-api"   //正式环境服务器地址
@@ -467,6 +467,10 @@ uint32_t ln_ota_get_task_http(const char *device_id,
                                 const char *device_token,
                                 const char *source_version);
 
+/* 与 ln_api_interface.h 中声明一致,供 C 模块包含 ln_api.h 时使用 */
+int ln_ota_version_string_is_1047(const char *version);
+void ln_ota_request_watch_ota_detection(const char *reason);
+
 uint32_t ln_ota_download_flow_http(const char *device_id,
                                     const char *device_token,
                                     const char *source_version);
index 700b75e..f6e2da5 100644 (file)
@@ -164,7 +164,7 @@ static void ln_ota_handle_download_msg(const OtaDownloadMsg *msg)
 }
 
 /* 目标版本为 1.0.47(可带 V 前缀、日期后缀如 1.0.47_20260427) */
-static int ln_ota_target_is_1047(const char *target)
+int ln_ota_version_string_is_1047(const char *target)
 {
     if (!target || target[0] == '\0') {
         return 0;
@@ -185,33 +185,80 @@ static int ln_ota_target_is_1047(const char *target)
     return 0;
 }
 
-/* 1.0.47 手表 OTA:须满足 Py34 前置(KV 或本次 Cat1 会话真升),不能仅口播已是 V34 */
-static int ln_ota_reject_1047_if_not_py34(const char *target)
+/* 是否处于 1.0.47 升级链路(当前手表版本或目标版本为 1.0.47) */
+static int ln_ota_on_1047_upgrade_track(const char *target_ver)
 {
-    return ln_ota_target_is_1047(target) && !ln_cat1_py34_prereq_met();
+    uint8_t major = 0;
+    uint8_t mid = 0;
+    uint8_t minor = 0;
+
+    if (uapi_get_fw_version(&major, &mid, &minor) == ERRCODE_SUCC &&
+        major == 1 && mid == 0 && minor == 47) {
+        return 1;
+    }
+    if (target_ver && ln_ota_version_string_is_1047(target_ver)) {
+        return 1;
+    }
+    return 0;
+}
+
+/** Cat1 未达标时禁止手表/蓝牙 OTA(仅 1.0.47 链路生效) */
+static int ln_ota_cat1_blocks_watch_ota(const char *target_ver)
+{
+    if (!ln_ota_on_1047_upgrade_track(target_ver)) {
+        return 0;
+    }
+    return !ln_cat1_may_proceed_to_watch_ota();
 }
 
 /* 无 4G 升级包且 Py34 未达标:提示用户联系售后 */
 static void ln_ota_notify_py34_unqualified_no_firmware(void)
 {
-    if (ln_cat1_py34_prereq_met()) {
+    if (ln_cat1_may_proceed_to_watch_ota()) {
         return;
     }
-    if (!ln_ota_target_is_1047(uapi_ln_get_device_version())) {
+    if (!ln_ota_on_1047_upgrade_track(NULL)) {
         return;
     }
     push_ota_status(OTA_UI_STATUS_CAT1_PY34_FAIL);
 }
 
+/** Cat1/modem+py 未达标时提示(仅 1.0.47 链路) */
+static void ln_ota_notify_watch_ota_blocked(void)
+{
+    if (ln_cat1_may_proceed_to_watch_ota() || !ln_ota_on_1047_upgrade_track(NULL)) {
+        return;
+    }
+    ln_ota_notify_py34_unqualified_no_firmware();
+}
+
+/** 唯一入口:modem 短串+py 达标后才发起手表/蓝牙 OTA getTask */
+void ln_ota_request_watch_ota_detection(const char *reason)
+{
+    if (ln_ota_cat1_blocks_watch_ota(NULL)) {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
+            "[OTA] watch OTA blocked (%s): profile_ok=%d py=%u (need EG8AK0409+V34+1.0.47)",
+            reason ? reason : "",
+            ln_cat1_already_at_v34_source_profile(),
+            (unsigned)uapi_get_py_ver());
+        ln_ota_notify_watch_ota_blocked();
+        return;
+    }
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] watch OTA detection allowed (%s)", reason ? reason : "");
+    do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK, NULL, 0);
+}
+
 //解析 OTA 任务 JSON,决定是否触发下载
 static void ln_handle_ota_task_response(const char *json_buf, int json_len)
 {
     WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析OTA升级任务JSON");
-    if (ln_ota_reject_1047_if_not_py34(uapi_ln_get_device_version()))
+    if (ln_ota_cat1_blocks_watch_ota(NULL))
     {
         WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
-            "[OTA] task rejected: 1.0.47 requires cat1 py34 (KV or verified session), py=%u kv=%d verified=%d",
-            (unsigned int)uapi_get_py_ver(), ln_kv_cat1_py34_upgrade_done(), uapi_cat1_ota_is_py34_verified());
+            "[OTA] getTask response ignored: modem/py not qualified, profile=%d py=%u",
+            ln_cat1_already_at_v34_source_profile(),
+            (unsigned int)uapi_get_py_ver());
+        ln_ota_notify_watch_ota_blocked();
         return;
     }
 
@@ -350,6 +397,16 @@ static void ln_handle_ota_task_response(const char *json_buf, int json_len)
     } else {
         push_upgrade_description_notification("");
     }
+
+    if (ln_ota_cat1_blocks_watch_ota(targetVersion->valuestring)) {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
+            "[OTA] getTask: cat1 not qualified, skip watch OTA UI (target=%s)",
+            targetVersion->valuestring);
+        ln_ota_notify_watch_ota_blocked();
+        cJSON_Delete(root);
+        free(text);
+        return;
+    }
     
     if (ut == 0)
     {
@@ -537,6 +594,21 @@ void ln_parse_device_upgrade(const char *json_buf, int json_len)
     } else {
         push_upgrade_description_notification("");
     }
+
+    if (ln_ota_cat1_blocks_watch_ota(targetVersion->valuestring)) {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
+            "[OTA] DeviceUpgrade blocked: cat1 not qualified (target=%s)",
+            targetVersion->valuestring);
+        ln_ws_send_device_upgrade_ack(
+            updateId->valuestring,
+            targetVersion->valuestring,
+            false,
+            "4G模组未达标,请先完成模组升级");
+        ln_ota_notify_watch_ota_blocked();
+        cJSON_Delete(root);
+        free(text);
+        return;
+    }
     
     if (ut == 0)
     {
@@ -568,6 +640,13 @@ uint32_t ln_ota_get_task_http(const char *device_id,
     if (!device_id || !device_token || !source_version)
         return ERRCODE_FAIL;
 
+    if (ln_ota_cat1_blocks_watch_ota(source_version)) {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
+            "[OTA] ln_ota_get_task_http blocked: cat1 not qualified (source=%s)", source_version);
+        ln_ota_notify_watch_ota_blocked();
+        return ERRCODE_FAIL;
+    }
+
 #ifdef SUPPORT_CAT1_SERVICE
     /* CAT1 保活 */
     cat1_api_add_cat1_app_vote();
@@ -1564,31 +1643,24 @@ void ln_is_cat1_ota_upgrade_check(void)
     printf("[OTA] ln_is_cat1_ota_upgrade_check: 开始检查CAT1 OTA升级\n");
     
 #ifdef SUPPORT_CAT1_SERVICE
+    /* 蓝牙准入:modem 短串 + py 已达标则只查手表 OTA */
+    if (ln_cat1_may_proceed_to_watch_ota()) {
+        ln_ota_request_watch_ota_detection("modem+py profile ok");
+        return;
+    }
+
     uint32_t ret = 0;
-    // 直接获取CAT1 modem版本(如果版本还未获取,会在后续处理中处理)
     char *text = cat1_api_runtime_get_cat1_modemver();
     if (!text || strlen(text) == 0) {
-        if (!ln_cat1_py34_prereq_met()) {
-            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
-                "[OTA] modem ver empty: py34 prereq not met, notify user");
-            ln_ota_notify_py34_unqualified_no_firmware();
-            return;
-        }
-        printf("[OTA] cat1 modem版本为空且py34已确认,执行普通OTA升级\n");
-        do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK, NULL, 0);
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] modem ver empty, wait Cat1 (no watch OTA)");
+        ln_ota_notify_py34_unqualified_no_firmware();
         return;
     }
     
     char version[16] = {0};
     if (get_modem_fw_version_c(text, version) != 0) {
-        if (!ln_cat1_py34_prereq_met()) {
-            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
-                "[OTA] modem ver parse fail: py34 prereq not met, notify user");
-            ln_ota_notify_py34_unqualified_no_firmware();
-            return;
-        }
-        printf("[OTA] 无法解析modem版本且py34已确认,执行普通OTA升级\n");
-        do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK, NULL, 0);
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] modem ver parse fail, wait Cat1");
+        ln_ota_notify_py34_unqualified_no_firmware();
         return;
     }
     
@@ -1624,21 +1696,19 @@ void ln_is_cat1_ota_upgrade_check(void)
     
     // 检查cat1模组升级url是否为空
     if(strlen(g_ln_last_package_url_new1) == 0 || strlen(g_ln_last_package_url_new2) == 0) {
-        if (!ln_cat1_py34_prereq_met()) {
+        if (ln_cat1_may_proceed_to_watch_ota()) {
+            ln_ota_request_watch_ota_detection("cat1 url empty but profile ok");
+        } else {
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
-                "[OTA] cat1 url empty: py34 prereq not met, notify user (py=%u kv=%d verified=%d)",
-                (unsigned)uapi_get_py_ver(), ln_kv_cat1_py34_upgrade_done(), uapi_cat1_ota_is_py34_verified());
+                "[OTA] cat1 url empty and profile not ok (py=%u)", (unsigned)uapi_get_py_ver());
             ln_ota_notify_py34_unqualified_no_firmware();
-            return;
         }
-        printf("[OTA] cat1模组升级url为空且py34已确认,执行普通OTA升级\n");
-        do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK, NULL, 0);
     } else {
         printf("[OTA] cat1模组升级url不为空,触发CAT1升级\n");
+        ln_cat1_ota_set_last_failed(0);
         push_ota_status(OTA_UI_STATUS_CAT1_START);
     }
 #else
-    // 不支持CAT1服务,直接执行普通OTA升级
-    do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK, NULL, 0);
+    ln_ota_request_watch_ota_detection("no cat1 service");
 #endif
 }
\ No newline at end of file
index 90973f7..8fcce45 100644 (file)
@@ -18,6 +18,7 @@ extern "C"
 #include <cstring>
 #include "kv_store.h"
 #include "cat1_pub_api.h"
+#include "custom_utils.h"
 
 #define LN_CAT1_PY34_MIN_VER         34
 #define LN_KV_CAT1_PY34_UPGRADE_DONE "cat1_py34_upgrade_done"
@@ -31,14 +32,54 @@ int ln_kv_cat1_py34_upgrade_done(void)
 
 int ln_cat1_py34_prereq_met(void)
 {
-    uint8_t py = uapi_get_py_ver();
-    if (py < LN_CAT1_PY34_MIN_VER && py != 32) {
-        return 0;
+    return ln_cat1_already_at_v34_source_profile();
+}
+
+static volatile int g_cat1_ota_last_failed = 0;
+
+void ln_cat1_ota_set_last_failed(int failed)
+{
+    g_cat1_ota_last_failed = failed ? 1 : 0;
+}
+
+int ln_cat1_ota_last_failed(void)
+{
+    return g_cat1_ota_last_failed;
+}
+
+int ln_cat1_may_proceed_to_watch_ota(void)
+{
+    uint8_t major = 0;
+    uint8_t mid = 0;
+    uint8_t minor = 0;
+
+    /* 非 1.0.47 手表软件:不做 Cat1 门禁 */
+    if (uapi_get_fw_version(&major, &mid, &minor) != ERRCODE_SUCC ||
+        major != 1 || mid != 0 || minor != 47) {
+        return 1;
     }
+    return ln_cat1_already_at_v34_source_profile();
+}
+
+int ln_cat1_ota_timer_check_success(void)
+{
     if (ln_cat1_already_at_v34_source_profile()) {
         return 1;
     }
-    return ln_kv_cat1_py34_upgrade_done() || uapi_cat1_ota_is_py34_verified();
+    {
+        int target = ln_cat1_last_package_urls_target_py();
+        uint8_t target_py = 34;
+        uint8_t cur_py = uapi_get_py_ver();
+        uint8_t baseline_py = uapi_cat1_ota_get_py_baseline();
+
+        if (target > 0 && target <= 255) {
+            target_py = (uint8_t)target;
+        }
+        if (cur_py >= target_py && cur_py > baseline_py) {
+            return 1;
+        }
+    }
+    return 0;
 }
 
 // 推送文本给ui
index 5ae9b21..3aefbad 100644 (file)
@@ -143,11 +143,28 @@ extern char g_ln_last_package_url_new2[256];
 int ln_cat1_last_package_urls_target_py(void);
 /** KV cat1_py34_upgrade_done 是否为 1 */
 int ln_kv_cat1_py34_upgrade_done(void);
-/** 本机已是 getTask 档位 47+EG8AK0409+V34(与 _1/_2 查询一致),无需 KV/verified */
+/**
+ * 蓝牙/手表 OTA 准入:实时 modem 短串(EG8AK0409) + py>=34 + 手表 1.0.47。
+ * 仅看当前版本,不看 KV / Cat1 会话成败。
+ */
 int ln_cat1_already_at_v34_source_profile(void);
-/** Cat1 已真升到 Py34:已是 V34 档位、KV 或本次 OTA verified */
+/** 是否允许进入手表/蓝牙 OTA 检测(1.0.47 同 already_at_v34;其它手表版本不限制) */
+int ln_cat1_may_proceed_to_watch_ota(void);
+/** Cat1 OTA 定时器:是否判定本轮 Cat1 升级成功(先完整 profile,否则 py 相对基线提升) */
+int ln_cat1_ota_timer_check_success(void);
+/** 记录本轮 Cat1 UI 失败(仅 UI/日志,不阻挡蓝牙准入) */
+void ln_cat1_ota_set_last_failed(int failed);
+int ln_cat1_ota_last_failed(void);
+/** @deprecated 仅旧逻辑保留;蓝牙准入请用 ln_cat1_may_proceed_to_watch_ota */
 int ln_cat1_py34_prereq_met(void);
 
+/** 版本字符串是否为 1.0.47(含 V 前缀、日期后缀) */
+int ln_ota_version_string_is_1047(const char *version);
+/**
+ * 唯一入口:在 Cat1 达标后才发起手表/蓝牙 OTA getTask 检测;未达标仅打日志/可选提示。
+ */
+void ln_ota_request_watch_ota_detection(const char *reason);
+
 #ifdef __cplusplus
 }
 #endif