优化产测
authorxue <2144439972@qq.com>
Sat, 28 Mar 2026 06:09:37 +0000 (14:09 +0800)
committerxue <2144439972@qq.com>
Sat, 28 Mar 2026 06:09:37 +0000 (14:09 +0800)
12 files changed:
code/sdk/zhihui/UI/producttest/include/testGPS.h
code/sdk/zhihui/UI/producttest/include/testPresenterC.h
code/sdk/zhihui/UI/producttest/include/testbt.h
code/sdk/zhihui/UI/producttest/src/testGPS.cpp
code/sdk/zhihui/UI/producttest/src/testPresenter.cpp
code/sdk/zhihui/UI/producttest/src/testbt.cpp
code/sdk/zhihui/UI/producttest/src/testgsenson.cpp
code/sdk/zhihui/utils/custom_cmd_service.c
code/sdk/zhihui/utils/custom_utils.c
code/sdk/zhihui/utils/custom_utils.h
code/sdk/zhihui/utils/custom_utils_at_cmd.c
code/sdk/zhihui/utilscpp/bt_scan_test.cpp

index 5cbc065..68cfc77 100644 (file)
@@ -74,6 +74,7 @@ namespace OHOS
         // 按钮状态管理函数
         void SetGPSButtonActive(bool isActive);
         void ToggleGPSState();  // 切换GPS开关状态
+        void SetGPSState(bool isOn); // 外部同步GPS状态(AT/协议触发)
     private:
         UILabelButtonExt *SetTheButton_ext(ButtonConfig_ext_t &buttonconfig);//, UIView::OnTouchListener *clickListener);
         UILabel *InitLabel(LabelConfig &labelConfig);
index 158522d..ee39aa1 100644 (file)
@@ -20,6 +20,19 @@ extern "C"
     void factorytest_enter_test_tp_view(void);
     void factorytest_enter_test_lcd_view(void);
     void factorytest_enter_test_button_view(void);
+    void factorytest_enter_test_luminance_view(void);
+    void factorytest_enter_test_charging_view(void);
+    void factorytest_enter_test_motor_view(void);
+    void factorytest_enter_test_hr_view(void);
+    void factorytest_enter_test_gsensor_view(void);
+    void factorytest_enter_test_bt_view(void);
+    void factorytest_enter_test_sechip_view(void);
+    void factorytest_enter_test_gps_view(void);
+    void factorytest_jump_to_subview(const char *item);
+    void factorytest_jump_to_subview_id(int16_t target);
+    void factorytest_set_gps_button_state(int on);
+    void factorytest_trigger_leaklight_ui(void);
+    void factorytest_set_bt_discovery_active(int active);
     
 #ifdef __cplusplus
 #if __cplusplus
index 293f629..7dfb59f 100644 (file)
@@ -65,6 +65,7 @@ namespace OHOS
         void set_BR_connect(char *);
         bool  br_scan_set_text(unsigned char *name ,unsigned char *addr,int16_t rssi,int i);
         UILabelButtonExt* button{NULL};
+        void SetDiscoveryActive(bool active); // 用于AT等外部同步按钮状态
 
     private:
         UILabelButtonExt *SetTheButton_ext(ButtonConfig_ext_t &buttonconfig);//, UIView::OnTouchListener *clickListener);
index e8e5105..663cf16 100644 (file)
@@ -143,6 +143,21 @@ namespace OHOS
         return button;
     }
     
+    void testGPSView::SetGPSState(bool isOn)
+    {
+        gpsIsOn = isOn;
+        SetGPSButtonActive(gpsIsOn);
+        if (gpsIsOn) {
+            if (getPGnssServerStat() != 1) {
+                gnss_start();
+            }
+        } else {
+            if (getPGnssServerStat() != 0) {
+                gnss_stop();
+            }
+        }
+    }
+
     void testGPSView::SetGPSButtonActive(bool isActive)
     {
         if (GPSbutton[0] != nullptr)
@@ -237,9 +252,13 @@ namespace OHOS
 
             if (getPGnssServerStat()==1)
             {
-                label_1_GPS_on_or_off->SetText("GPS开");
+                if (label_1_GPS_on_or_off != nullptr) {
+                    label_1_GPS_on_or_off->SetText("GPS开");
+                }
             }else{
-                label_1_GPS_on_or_off->SetText("GPS关");
+                if (label_1_GPS_on_or_off != nullptr) {
+                    label_1_GPS_on_or_off->SetText("GPS关");
+                }
             }
         }
     }
index cc25dba..71ee5ac 100644 (file)
@@ -76,6 +76,7 @@ namespace OHOS
 
     // 当前Presenter属性实例化的内存指针
     static testPresenter *g_pSystemPresenter = nullptr;
+    static int16_t g_pendingJumpSubView = -1; // TEST_VIEW enum value, -1 means none
 
     testPresenter::testPresenter()
     {
@@ -101,6 +102,12 @@ namespace OHOS
     }
     void testPresenter::OnResume()
     {
+        // 如果有待跳转的子界面(来自 AT / 协议),在 slice resume 后再执行,避免 testView 尚未创建导致跳转丢失
+        if (g_pendingJumpSubView >= 0 && testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, g_pendingJumpSubView);
+            g_pendingJumpSubView = -1;
+        }
+
         if(view_->GetSystemScrollView(BUTTON) != nullptr)
         {
             RootView::GetInstance()->SetOnKeyActListener(this);
@@ -553,6 +560,8 @@ extern "C"
 #endif /* __cplusplus */
 #endif /* __cplusplus */
 
+    void factorytest_jump_to_subview_id(int16_t target);
+
     void factorytest_enter_test_tp_view(void)
     {
         if(testView::GetInstance())
@@ -574,6 +583,145 @@ extern "C"
         testView::GetInstance()->ChangeView(TESTVIEW,BUTTON);
     }
 
+    void factorytest_enter_test_luminance_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, LUMINANCE);
+        }
+    }
+
+    void factorytest_enter_test_charging_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, CHARG);
+        }
+    }
+
+    void factorytest_enter_test_motor_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, MOTOR);
+        }
+    }
+
+    void factorytest_enter_test_hr_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, HRSENSON);
+        }
+    }
+
+    void factorytest_enter_test_gsensor_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, GSENSON);
+        }
+    }
+
+    void factorytest_enter_test_bt_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, BTTEST);
+        }
+    }
+
+    void factorytest_enter_test_sechip_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, SEChipTest);
+        }
+    }
+
+    void factorytest_enter_test_gps_view(void)
+    {
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, GPSTEST);
+        }
+    }
+
+    void factorytest_jump_to_subview(const char *item)
+    {
+        if (item == nullptr) {
+            return;
+        }
+
+        int16_t target = -1;
+        if (strcasecmp(item, "TP") == 0) {
+            target = TP;
+        } else if (strcasecmp(item, "BRIGHT") == 0) {
+            target = LUMINANCE;
+        } else if (strcasecmp(item, "CHARGE") == 0) {
+            target = CHARG;
+        } else if (strcasecmp(item, "MOTOR") == 0) {
+            target = MOTOR;
+        } else if (strcasecmp(item, "KEY") == 0) {
+            target = BUTTON;
+        } else if (strcasecmp(item, "BUTTON") == 0) {
+            target = BUTTON;
+        } else if (strcasecmp(item, "LCD") == 0) {
+            target = LCD;
+        } else if (strcasecmp(item, "LEAKLIGHT") == 0) {
+            target = HRSENSON;
+        } else if (strcasecmp(item, "GSENSOR") == 0) {
+            target = GSENSON;
+        } else if (strcasecmp(item, "BT") == 0) {
+            target = BTTEST;
+        } else if (strcasecmp(item, "SE") == 0) {
+            target = SEChipTest;
+        } else if (strcasecmp(item, "GPS") == 0) {
+            target = GPSTEST;
+        }
+
+        if (target < 0) {
+            return;
+        }
+
+        factorytest_jump_to_subview_id(target);
+    }
+
+    void factorytest_jump_to_subview_id(int16_t target)
+    {
+        if (target < 0) {
+            return;
+        }
+        if (testView::GetInstance()) {
+            testView::GetInstance()->ChangeView(TESTVIEW, target);
+        } else {
+            g_pendingJumpSubView = target;
+        }
+    }
+
+    void factorytest_set_gps_button_state(int on)
+    {
+#if defined(SUPPORT_GNSS)
+        testGPSView *gpsView = testGPSView::GetInstance();
+        if (gpsView != nullptr) {
+            gpsView->SetGPSState(on ? true : false);
+        }
+#else
+        (void)on;
+#endif
+    }
+
+    void factorytest_trigger_leaklight_ui(void)
+    {
+#if defined(SUPPORT_PPG_SERVICE)
+        testhrsenson1View *hrView = testhrsenson1View::GetInstance();
+        if (hrView != nullptr) {
+            // 等价于界面“light”按钮的行为:获取漏光并刷新文本
+            hrView->setlightvalue();
+        }
+#endif
+    }
+
+    void factorytest_set_bt_discovery_active(int active)
+    {
+        testBTView *btView = testBTView::GetInstance();
+        if (btView != nullptr) {
+            btView->SetDiscoveryActive(active ? true : false);
+        }
+    }
+
 #ifdef __cplusplus
 #if __cplusplus
 }
index 7d18a6c..35487c1 100644 (file)
@@ -165,6 +165,21 @@ namespace OHOS
     static bool speakfalg=false;
    
     static bool discovery_falg=false;
+
+    void testBTView::SetDiscoveryActive(bool active)
+    {
+        discovery_falg = active;
+        if (button != nullptr) {
+            if (active) {
+                button->SetText("scaning");
+                button->SetStyle(STYLE_BACKGROUND_COLOR, Color::Green().full);
+            } else {
+                button->SetText("discovery");
+                button->SetStyle(STYLE_BACKGROUND_COLOR, Color::Blue().full);
+            }
+            RootView::GetInstance()->Invalidate();
+        }
+    }
    
     void testBTView:: BT_discovery_callback( void)
     {
index da1898f..e6804a2 100644 (file)
@@ -136,7 +136,10 @@ namespace OHOS
 
     static void  mgdataTimerCallBack(int data)
     {
-         testgsensonView::GetInstance()->set_data();
+        testgsensonView *p = testgsensonView::GetInstance();
+        if (p != nullptr) {
+            p->set_data();
+        }
     }
     static uint8_t MG_TEST_FALG=0;
     void testgsensonView ::set_data(void)
@@ -180,8 +183,10 @@ namespace OHOS
 
 #endif
 
-        x->SetText(buf);
-        RootView::GetInstance()->Invalidate();
+        if (x != nullptr) {
+            x->SetText(buf);
+            RootView::GetInstance()->Invalidate();
+        }
 
         // if(MG_TEST_FALG>10)
         // CompassModel::GetInstance()->CloseCompassSensor();
index 8661893..9463b1c 100644 (file)
@@ -314,6 +314,27 @@ static void custom_cmd_service_check_task(void const *arg)
             {
                 uapi_factorytest_enter_test_view(0);
             }
+#endif
+            break;
+        case CUSTOM_CMD_MSG_TYPE_PRODUCTTEST_JUMP:
+#ifdef SUPPORT_FACTORY_SERVICE
+            // 仅做子界面跳转:外部会先执行 AT^ENTERTESTVIEW 进入产测slice,
+            // 这里不再重复进入,避免 Stop/Start Slice 导致界面重建、跳转丢失。
+            // data[0] 为子界面编码(与AT^TESTJUMP的item对应),避免C侧依赖C++枚举值
+            switch (mail.data[0]) {
+                case 1: uapi_factorytest_jump_to_subview("TP"); break;
+                case 2: uapi_factorytest_jump_to_subview("BRIGHT"); break;
+                case 3: uapi_factorytest_jump_to_subview("CHARGE"); break;
+                case 4: uapi_factorytest_jump_to_subview("MOTOR"); break;
+                case 5: uapi_factorytest_jump_to_subview("KEY"); break;
+                case 6: uapi_factorytest_jump_to_subview("LEAKLIGHT"); break;
+                case 7: uapi_factorytest_jump_to_subview("GSENSOR"); break;
+                case 8: uapi_factorytest_jump_to_subview("BT"); break;
+                case 9: uapi_factorytest_jump_to_subview("SE"); break;
+                case 10: uapi_factorytest_jump_to_subview("GPS"); break;
+                case 11: uapi_factorytest_jump_to_subview("LCD"); break;
+                default: break;
+            }
 #endif
             break;
         case CUSTOM_CMD_MSG_TYPE_VIBRATOR_TEST:
index 42c64cd..c9bbc04 100644 (file)
@@ -3181,6 +3181,105 @@ void uapi_factorytest_enter_test_button_view(void)
     #endif
 }
 
+void uapi_factorytest_enter_test_luminance_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_luminance_view();
+    #endif
+}
+
+void uapi_factorytest_enter_test_charging_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_charging_view();
+    #endif
+}
+
+void uapi_factorytest_enter_test_motor_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_motor_view();
+    #endif
+}
+
+void uapi_factorytest_enter_test_hr_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_hr_view();
+    #endif
+}
+
+void uapi_factorytest_enter_test_gsensor_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_gsensor_view();
+    #endif
+}
+
+void uapi_factorytest_enter_test_bt_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_bt_view();
+    #endif
+}
+
+void uapi_factorytest_enter_test_sechip_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_sechip_view();
+    #endif
+}
+
+void uapi_factorytest_enter_test_gps_view(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_enter_test_gps_view();
+    #endif
+}
+
+void uapi_factorytest_jump_to_subview(const char *item)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_jump_to_subview(item);
+    #else
+        (void)item;
+    #endif
+}
+
+void uapi_factorytest_jump_to_subview_id(int16_t target)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_jump_to_subview_id(target);
+    #else
+        (void)target;
+    #endif
+}
+
+void uapi_factorytest_set_gps_button_state(int on)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_set_gps_button_state(on);
+    #else
+        (void)on;
+    #endif
+}
+
+void uapi_factorytest_trigger_leaklight_ui(void)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_trigger_leaklight_ui();
+    #endif
+}
+
+void uapi_factorytest_set_bt_discovery_active(int active)
+{
+    #ifdef CUSTOM_ZHIHUI
+        factorytest_set_bt_discovery_active(active);
+    #else
+        (void)active;
+    #endif
+}
+
 void uapi_factorytest_hide_test_lcd_button(void)
 {
     #ifdef CUSTOM_ZHIHUI
index 2e9d81e..a1b0765 100644 (file)
@@ -267,6 +267,7 @@ uint32_t uapi_calendar_store_date(void);
 #define CUSTOM_CMD_MSG_STOP_SECOND_STREAM_IN 0x2C
 
 #define CUSTOM_CMD_MSG_TYPE_CAT1_RESTART 0x2D
+#define CUSTOM_CMD_MSG_TYPE_PRODUCTTEST_JUMP 0x2E
 
 typedef enum {
     BR_HFP_CONNECT=1,
@@ -368,6 +369,19 @@ void uapi_factorytest_enter_test_view(int cmd);
 void uapi_factorytest_enter_test_tp_view(void);
 void uapi_factorytest_enter_test_lcd_view(void);
 void uapi_factorytest_enter_test_button_view(void);
+void uapi_factorytest_enter_test_luminance_view(void);
+void uapi_factorytest_enter_test_charging_view(void);
+void uapi_factorytest_enter_test_motor_view(void);
+void uapi_factorytest_enter_test_hr_view(void);
+void uapi_factorytest_enter_test_gsensor_view(void);
+void uapi_factorytest_enter_test_bt_view(void);
+void uapi_factorytest_enter_test_sechip_view(void);
+void uapi_factorytest_enter_test_gps_view(void);
+void uapi_factorytest_jump_to_subview(const char *item);
+void uapi_factorytest_jump_to_subview_id(int16_t target);
+void uapi_factorytest_set_gps_button_state(int on);
+void uapi_factorytest_trigger_leaklight_ui(void);
+void uapi_factorytest_set_bt_discovery_active(int active);
 void uapi_factorytest_hide_test_lcd_button(void);
 void uapi_factorytest_hide_test_tp_button(void);
 void uapi_efuse_test_write(uint8_t *data,uint32_t byte_number,uint8_t len);
index 0cf7f34..3e79daa 100644 (file)
 #endif
 
 #ifdef SUPPORT_PPG_SERVICE
+#include "ppg_service.h"
 #include "ppg_hal.h"
 #endif
 
+/* 马达振动测试所需结构体/接口 */
+#include "vibrator_service.h"
+
 /* Include custom_utilscpp.h for SPI1 receive functions */
 #include "custom_utilscpp.h"
 /* Include spi_protocol_test.h for protocol test functions */
 #include "custom_save_runtime.h"
 #include "gnss_http_server.h"
 
+#ifdef SUPPORT_GNSS
+#include "gnss_ctl.h"
+#include "NMEA0183.h"
+#include "pgnss_sample.h"
+#endif
+
 static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen,
         uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN], uint32_t* argc)
 {
@@ -1037,6 +1047,414 @@ static uint32_t custom_get_imei(uint8_t *para, uint32_t paraLen, char *strCommon
     return ERRCODE_SUCC;
 }
 
+/* 获取设备固件版本(buildversion) */
+static uint32_t custom_get_fw_version(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(para);
+    UNUSED(paraLen);
+    UNUSED(strCommonCmd);
+
+    char version_buf[128] = {0};
+    uint32_t version_len = sizeof(version_buf) - 1; // 预留结束符
+    errcode_t ret = uapi_get_buildversion((uint8_t *)version_buf, version_len);
+    if (ret == ERRCODE_SUCC) {
+        wstp_print("fw_version:%s\r\n", version_buf);
+    } else {
+        wstp_print("fw_version:ERR ret=%d\r\n", (int)ret);
+    }
+    return ERRCODE_SUCC;
+}
+
+/* 进入指定产测子界面(会先进入产测Slice) */
+static uint32_t custom_producttest_jump(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(strCommonCmd);
+    uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 };
+    uint32_t argc = 0;
+
+    int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+    if (ret != CUSTOM_UTILS_SUCCESS) {
+        wstp_print("TESTJUMP ATCMD Convert Command Failed\r\n");
+        return ERRCODE_FAIL;
+    }
+
+    if (argc != 1 || strcmp((char*)argv[0], "?") == 0) {
+        wstp_print("Usage: AT^TESTJUMP=<item>\r\n");
+        wstp_print("  item: TP|LCD|BRIGHT|CHARGE|MOTOR|KEY|BUTTON|LEAKLIGHT|GSENSOR|BT|SE|GPS\r\n");
+        return ERRCODE_SUCC;
+    }
+
+    const char *item = (const char*)argv[0];
+    uint8_t code = 0xFF;
+    if (strcasecmp(item, "TP") == 0) {
+        code = 1;
+    } else if (strcasecmp(item, "BRIGHT") == 0) {
+        code = 2;
+    } else if (strcasecmp(item, "CHARGE") == 0) {
+        code = 3;
+    } else if (strcasecmp(item, "MOTOR") == 0) {
+        code = 4;
+    } else if (strcasecmp(item, "KEY") == 0) {
+        code = 5;
+    } else if (strcasecmp(item, "BUTTON") == 0) {
+        code = 5;
+    } else if (strcasecmp(item, "LCD") == 0) {
+        code = 11;
+    } else if (strcasecmp(item, "LEAKLIGHT") == 0) {
+        code = 6;
+    } else if (strcasecmp(item, "GSENSOR") == 0) {
+        code = 7;
+    } else if (strcasecmp(item, "BT") == 0) {
+        code = 8;
+    } else if (strcasecmp(item, "SE") == 0) {
+        code = 9;
+    } else if (strcasecmp(item, "GPS") == 0) {
+        code = 10;
+    } else {
+        wstp_print("TESTJUMP: unknown item=%s\r\n", item);
+        wstp_print("Usage: AT^TESTJUMP=<item>\r\n");
+        wstp_print("  item: TP|LCD|BRIGHT|CHARGE|MOTOR|KEY|BUTTON|LEAKLIGHT|GSENSOR|BT|SE|GPS\r\n");
+        return ERRCODE_FAIL;
+    }
+
+    // 参考 AT^LCDTEST=1 的实现:通过 custom_cmd_service 异步执行跳转,避免切 slice 时机问题
+    do_custom_cmd_service_check_msg(CUSTOM_CMD_MSG_TYPE_PRODUCTTEST_JUMP, &code, sizeof(code));
+    wstp_print("TESTJUMP: %s\r\n", item);
+    return ERRCODE_SUCC;
+}
+
+#ifdef SUPPORT_GNSS
+/* AT^GPSVIEW - jump to GPS test subview (need AT^ENTERTESTVIEW first) */
+static uint32_t custom_gps_view(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(strCommonCmd);
+    UNUSED(para);
+    UNUSED(paraLen);
+    uint8_t code = 10; // GPS (CUSTOM_CMD_MSG_TYPE_PRODUCTTEST_JUMP code)
+    do_custom_cmd_service_check_msg(CUSTOM_CMD_MSG_TYPE_PRODUCTTEST_JUMP, &code, sizeof(code));
+    wstp_print("GPSVIEW\r\n");
+    return ERRCODE_SUCC;
+}
+
+/* AT^GPS=1/0 - start/stop GNSS and sync GPS button state if view exists */
+static uint32_t custom_gps_switch(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(strCommonCmd);
+    uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 };
+    uint32_t argc = 0;
+
+    int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+    if (ret != CUSTOM_UTILS_SUCCESS) {
+        wstp_print("GPS ATCMD Convert Command Failed\r\n");
+        return ERRCODE_FAIL;
+    }
+    if (argc != 1 || strcmp((char*)argv[0], "?") == 0) {
+        wstp_print("Usage: AT^GPS=1/0\r\n");
+        return ERRCODE_SUCC;
+    }
+
+    int on = atoi((char*)argv[0]) ? 1 : 0;
+    if (on) {
+        if (getPGnssServerStat() == 1) {
+            wstp_print("GPS: already on\r\n");
+        } else {
+            errcode_t r = gnss_start();
+            wstp_print("GPS: on ret=%d\r\n", (int)r);
+        }
+    } else {
+        if (getPGnssServerStat() == 0) {
+            wstp_print("GPS: already off\r\n");
+        } else {
+            errcode_t r = gnss_stop();
+            wstp_print("GPS: off ret=%d\r\n", (int)r);
+        }
+    }
+
+    // sync button UI state (if GPS test view is shown)
+    uapi_factorytest_set_gps_button_state(on);
+    return ERRCODE_SUCC;
+}
+
+/* AT^GPSINFO - print satellite/CN0 info and basic fields */
+static uint32_t custom_gps_info(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(strCommonCmd);
+    UNUSED(para);
+    UNUSED(paraLen);
+
+    gps_data_t *gpsdata = getPGPSGlobaldata();
+    if (gpsdata == NULL) {
+        wstp_print("GPSINFO: gpsdata=null\r\n");
+        return ERRCODE_FAIL;
+    }
+    gps_data_t gpsd = {0};
+    if (gpsdata->last_gps_time_ms > 0) {
+        memcpy(&gpsd, gpsdata, sizeof(gps_data_t));
+    } else {
+        memcpy(&gpsd, gpsdata, sizeof(gps_data_t));
+    }
+
+    char buf[1024] = {0};
+    dump_location_data_to_buf(&gpsd, buf, (int)sizeof(buf));
+
+    // 统计当前已解析到的最大CN0/SNR(用于产测快速判断信号强度)
+    int max_cn0 = 0;
+    const start_stat_t *groups[] = { gpsd.startsP, gpsd.startsB, gpsd.startsN, gpsd.startsL, gpsd.startsQ, gpsd.startsA };
+    for (unsigned gi = 0; gi < (unsigned)(sizeof(groups) / sizeof(groups[0])); ++gi) {
+        for (int i = 0; i < 5; ++i) {
+            if (groups[gi][i].starID > 0 && groups[gi][i].snr > max_cn0) {
+                max_cn0 = groups[gi][i].snr;
+            }
+        }
+    }
+
+    /* 首行汇总:sats=GGA 参与定位卫星数;trace/valid_eph/err 来自 $PNTH001(模组 PNT 跟踪星、有效星历、失败码) */
+    wstp_print("GPSINFO: stat=%d, sats=%d, trace=%d, valid_ephemeris=%d, err=%d, vga=%d, quality=%d.%02d, max_cn0=%d, last_ms=%u\r\n",
+               (int)getPGnssServerStat(),
+               (int)gpsd.num_sats,
+               (int)gpsd.th001_trace_star_count,
+               (int)gpsd.th001_valid_star_count,
+               (int)gpsd.th001_errorcode,
+               (int)gpsd.th000_signal_vga,
+               (int)gpsd.th001_quality,
+               (int)((gpsd.th001_quality - (int)gpsd.th001_quality) * 100),
+               (int)max_cn0,
+               (unsigned int)gpsd.last_gps_time_ms);
+    wstp_print("%s\r\n", buf);
+    return ERRCODE_SUCC;
+}
+#endif
+
+/* 马达振动(等价于界面 start 按钮的一次振动参数) */
+static uint32_t custom_motor_vibrate_once(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(para);
+    UNUSED(paraLen);
+    UNUSED(strCommonCmd);
+
+    vibrator_cmd_t mail = {0};
+    mail.cycle_time_ms = 235;
+    mail.duty = 60;
+    mail.duration_time_ms = 100;
+    do_vibrator_cmd(mail);
+    wstp_print("MOTOR: vibrate_once\r\n");
+    return ERRCODE_SUCC;
+}
+
+/* 心率漏光测试按钮(Light / leaklight) */
+static uint32_t custom_leaklight_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(para);
+    UNUSED(paraLen);
+    UNUSED(strCommonCmd);
+
+    // 进入心率漏光界面
+    uapi_factorytest_enter_test_view(1);
+    uapi_factorytest_enter_test_hr_view();
+
+#ifdef SUPPORT_PPG_SERVICE
+    send_ppg_cmd_msg(PPG_SERVICE_CMD_TYPE_DO_LEAKLIGHT_TEST);
+    LOS_Msleep(1000);
+    uint32_t ch0 = 0, ch1 = 0, ch2 = 0;
+    ppg_get_factory_data(&ch0, &ch1, &ch2, NULL);
+    send_ppg_cmd_msg(PPG_SERVICE_CMD_TYPE_STOP_PPG_TEST);
+    wstp_print(">>>leaklight:green=%u,red=%u,ir=%u\r\n", (unsigned int)ch0, (unsigned int)ch1, (unsigned int)ch2);
+    // 同步UI(若当前正在显示心率漏光界面)
+    uapi_factorytest_trigger_leaklight_ui();
+#else
+    wstp_print("LEAKLIGHT: not supported (SUPPORT_PPG_SERVICE disabled)\r\n");
+#endif
+    return ERRCODE_SUCC;
+}
+
+/* 亮度渐变:0-100% 约 3 秒内完成,用于产测 */
+#define BRIGHTRAMP_STEPS      30
+#define BRIGHTRAMP_DURATION_MS 3000
+#define BRIGHTRAMP_MIN        51
+#define BRIGHTRAMP_MAX        255
+
+static osTimerId_t g_brightramp_timer = NULL;
+static uint8_t g_brightramp_step = 0;
+
+static void brightramp_timer_cb(void *arg)
+{
+    (void)arg;
+    if (g_brightramp_step >= BRIGHTRAMP_STEPS) {
+        if (g_brightramp_timer) {
+            osTimerStop(g_brightramp_timer);
+            osTimerDelete(g_brightramp_timer);
+            g_brightramp_timer = NULL;
+        }
+        uapi_setScreenBrightness(BRIGHTRAMP_MAX);
+        wstp_print("BRIGHTRAMP: done (100%%)\r\n");
+        return;
+    }
+    /* 0-100 线性映射到 BRIGHTRAMP_MIN(51)-BRIGHTRAMP_MAX(255) */
+    uint8_t percent = (g_brightramp_step * 100) / BRIGHTRAMP_STEPS;
+    int value = BRIGHTRAMP_MIN + ((BRIGHTRAMP_MAX - BRIGHTRAMP_MIN) * percent) / 100;
+    if (value > BRIGHTRAMP_MAX) {
+        value = BRIGHTRAMP_MAX;
+    }
+    uapi_setScreenBrightness(value);
+    g_brightramp_step++;
+}
+
+static uint32_t custom_brightramp(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(para);
+    UNUSED(paraLen);
+    UNUSED(strCommonCmd);
+
+    if (g_brightramp_timer) {
+        osTimerStop(g_brightramp_timer);
+        osTimerDelete(g_brightramp_timer);
+        g_brightramp_timer = NULL;
+    }
+
+    g_brightramp_step = 0;
+    uapi_setScreenBrightness(BRIGHTRAMP_MIN);
+
+    g_brightramp_timer = osTimerNew(brightramp_timer_cb, osTimerPeriodic, NULL, NULL);
+    if (g_brightramp_timer == NULL) {
+        wstp_print("BRIGHTRAMP: timer create failed\r\n");
+        return ERRCODE_FAIL;
+    }
+    osTimerStart(g_brightramp_timer, BRIGHTRAMP_DURATION_MS / BRIGHTRAMP_STEPS);
+    wstp_print("BRIGHTRAMP: 0%% -> 100%% in %d s\r\n", BRIGHTRAMP_DURATION_MS / 1000);
+    return ERRCODE_SUCC;
+}
+
+/* GSENSOR 每秒打印 XYZ,不切换 UI */
+static osTimerId_t g_gsensor_at_timer = NULL;
+static void gsensor_at_timer_cb(void *arg)
+{
+    (void)arg;
+#ifdef SUPPORT_CYWEE_SERVICE
+    gsenson_t t;
+    cywee_get_gsensor_data(1, &t);
+    printf(">>>XYZ: X=%.2f,Y=%.2f,Z=%.2f\r\n", t.x, t.y, t.z);
+#endif
+}
+
+static uint32_t custom_gsensor_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(strCommonCmd);
+    uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 };
+    uint32_t argc = 0;
+
+    int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+    if (ret != CUSTOM_UTILS_SUCCESS) {
+        wstp_print("GSENSORTEST ATCMD Convert Command Failed\r\n");
+        return ERRCODE_FAIL;
+    }
+
+    if (argc != 1 || strcmp((char*)argv[0], "?") == 0) {
+        wstp_print("Usage: AT^GSENSORTEST=1/0\r\n");
+        return ERRCODE_SUCC;
+    }
+
+    uint8_t cmd = (uint8_t)atoi((char*)argv[0]);
+    if (cmd == 1) {
+#ifdef SUPPORT_CYWEE_SERVICE
+        if (g_gsensor_at_timer == NULL) {
+            g_gsensor_at_timer = osTimerNew(gsensor_at_timer_cb, osTimerPeriodic, NULL, NULL);
+        }
+        if (g_gsensor_at_timer) {
+            osTimerStart(g_gsensor_at_timer, 1000);
+            wstp_print("GSENSORTEST: start (print xyz every 1s)\r\n");
+        } else {
+            wstp_print("GSENSORTEST: start failed (timer null)\r\n");
+        }
+#else
+        wstp_print("GSENSORTEST: not supported (SUPPORT_CYWEE_SERVICE disabled)\r\n");
+#endif
+    } else {
+        if (g_gsensor_at_timer) {
+            osTimerStop(g_gsensor_at_timer);
+            osTimerDelete(g_gsensor_at_timer);
+            g_gsensor_at_timer = NULL;
+        }
+        wstp_print("GSENSORTEST: stop\r\n");
+    }
+
+    return ERRCODE_SUCC;
+}
+
+/* 蓝牙测试界面:启用/禁用蓝牙栈并跳转界面(注意:系统已有 AT^BTENABLE/BTDISABLE) */
+static uint32_t custom_bt_enable_view(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(para);
+    UNUSED(paraLen);
+    UNUSED(strCommonCmd);
+
+    // 启用蓝牙栈
+    errcode_t res = enable_bt_stack();
+    wstp_print("BTENABLEVIEW: enable_bt_stack ret=%d\r\n", (int)res);
+    uapi_factorytest_enter_test_view(1);
+    uapi_factorytest_enter_test_bt_view();
+    // 同步UI:默认处于未扫描(discovery)状态
+    uapi_factorytest_set_bt_discovery_active(0);
+    return ERRCODE_SUCC;
+}
+
+static uint32_t custom_bt_disable_view(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(para);
+    UNUSED(paraLen);
+    UNUSED(strCommonCmd);
+
+    // 先停扫描(若正在扫)
+    extern int custom_bt_scan_test_stop(void);
+    (void)custom_bt_scan_test_stop();
+
+    errcode_t res = disable_bt_stack();
+    wstp_print("BTDISABLEVIEW: disable_bt_stack ret=%d\r\n", (int)res);
+    // 同步UI:退出扫描态
+    uapi_factorytest_set_bt_discovery_active(0);
+    return ERRCODE_SUCC;
+}
+
+/* 直接进入/退出产测界面(不重启) */
+static uint32_t custom_enter_producttest_view(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(strCommonCmd);
+    uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 };
+    uint32_t argc = 0;
+
+    int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+    if (ret != CUSTOM_UTILS_SUCCESS) {
+        wstp_print("ENTERTESTVIEW ATCMD Convert Command Failed\r\n");
+        return ERRCODE_FAIL;
+    }
+
+    /* 默认进入:AT^ENTERTESTVIEW 或 AT^ENTERTESTVIEW=1 */
+    uint8_t cmd = 1;
+    if (argc == 0) {
+        cmd = 1;
+    } else if (argc == 1) {
+        if (strcmp((char*)argv[0], "?") == 0) {
+            wstp_print("Usage: AT^ENTERTESTVIEW[=1|0]\r\n");
+            wstp_print("  1: enter producttest UI\r\n");
+            wstp_print("  0: exit producttest UI\r\n");
+            return ERRCODE_SUCC;
+        }
+        cmd = (uint8_t)atoi((char*)argv[0]);
+    } else {
+        wstp_print("ENTERTESTVIEW ATCMD Set Parameter Failed argc %d\r\n", argc);
+        wstp_print("Usage: AT^ENTERTESTVIEW[=1|0]\r\n");
+        return ERRCODE_FAIL;
+    }
+
+    if (cmd != 0 && cmd != 1) {
+        wstp_print("ENTERTESTVIEW invalid cmd=%d (must be 0 or 1)\r\n", cmd);
+        return ERRCODE_FAIL;
+    }
+
+    uapi_factorytest_enter_test_view(cmd);
+    wstp_print("ENTERTESTVIEW: %s\r\n", (cmd == 1) ? "enter" : "exit");
+    return ERRCODE_SUCC;
+}
+
 extern void lsfd(void);
 static uint32_t custom_lsfd(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 {
@@ -2320,6 +2738,20 @@ static const at_cmd_table_t g_custom_utils_cmd[] = {
     { (char *)"AT^CUSTOM_SN",      custom_utils_setsn, (char *)"123456789ABCDEF2345678" },
     { (char *)"AT^CUSTOM_SN_REQ",  custom_utils_getsn, (char *)" " },
     { (char *)"AT^STARTOTA",      custom_start_ota, (char *)" " },
+    { (char *)"AT^GETFWVER",      custom_get_fw_version, (char *)"get firmware version (buildversion)" },
+    { (char *)"AT^ENTERTESTVIEW", custom_enter_producttest_view, (char *)"enter/exit producttest UI: AT^ENTERTESTVIEW[=1|0]" },
+    { (char *)"AT^TESTJUMP",      custom_producttest_jump, (char *)"jump producttest subview: AT^TESTJUMP=TP|LCD|BRIGHT|CHARGE|MOTOR|KEY|BUTTON|LEAKLIGHT|GSENSOR|BT|SE|GPS" },
+#ifdef SUPPORT_GNSS
+    { (char *)"AT^GPSVIEW",       custom_gps_view, (char *)"enter gps test subview (need AT^ENTERTESTVIEW first)" },
+    { (char *)"AT^GPS",           custom_gps_switch, (char *)"gps switch (sync UI button): AT^GPS=1/0" },
+    { (char *)"AT^GPSINFO",       custom_gps_info, (char *)"GPS summary + GSV(svid,snr) + PNT trace/valid_eph/err" },
+#endif
+    { (char *)"AT^MOTOR",         custom_motor_vibrate_once, (char *)"motor vibrate once (same as UI start)" },
+    { (char *)"AT^LEAKLIGHT",     custom_leaklight_test, (char *)"hr leaklight button (Light): AT^LEAKLIGHT" },
+    { (char *)"AT^GSENSORTEST",   custom_gsensor_test, (char *)"gsensor test print xyz every 1s: AT^GSENSORTEST=1/0" },
+    { (char *)"AT^BRIGHTRAMP",    custom_brightramp,   (char *)"auto ramp brightness 0->100%% in ~3s: AT^BRIGHTRAMP" },
+    { (char *)"AT^BTENABLEVIEW",  custom_bt_enable_view, (char *)"enable bt stack and enter BT test view" },
+    { (char *)"AT^BTDISABLEVIEW", custom_bt_disable_view, (char *)"stop scan and disable bt stack" },
     { (char *)"AT^LCDID",      custom_lcd_getid, (char *)" " },
 
     { (char *)"AT^TOUCHID",      custom_touch_getid, (char *)" " },
index ef9dfe8..60b6204 100644 (file)
@@ -174,14 +174,24 @@ static void BtScanDiscoveryResultCallback(const bd_addr_t *bd_addr)
     }
     MediaMutexUnLock(g_bt_scan_ctx.mutex);
     
-    if (is_new) {
-        PRINT("[BtScan] New device discovered: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
-              bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
-              bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0]);
-    }
+    // 这里只做“新设备发现”标记,等拿到 RSSI 后再按产测格式打印
     
     // 添加设备(暂时没有名称和RSSI)
-    BtScanAddOrUpdateDevice(bd_addr, 0, NULL, false);
+    if (is_new) {
+        // 先加一条空记录,让 device_count 统计包含它
+        BtScanAddOrUpdateDevice(bd_addr, 0, NULL, false);
+        // 标记为新设备(用于在第一次拿到RSSI时打印“新蓝牙设备”)
+        MediaMutexLock(g_bt_scan_ctx.mutex);
+        for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+            if (memcmp(g_bt_scan_ctx.devices[i].addr, bd_addr->addr, BD_ADDR_LEN) == 0) {
+                g_bt_scan_ctx.devices[i].is_new_device = true;
+                break;
+            }
+        }
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+    } else {
+        BtScanAddOrUpdateDevice(bd_addr, 0, NULL, false);
+    }
     
     // 尝试获取设备名称(只在设备没有名称时才获取和打印)
     if (!has_name_already) {
@@ -227,6 +237,35 @@ static void BtScanReadRemoteRssiCallback(const bd_addr_t *bd_addr, int rssi, int
     }
     
     if (status == 0) {
+        // 产测格式打印:每次检测到RSSI都打印一行;新设备首次拿到RSSI时额外打印“新蓝牙设备...已检测到X个不同设备”
+        bool is_new_device = false;
+        uint32_t unique_count = 0;
+        MediaMutexLock(g_bt_scan_ctx.mutex);
+        unique_count = g_bt_scan_ctx.device_count;
+        for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+            if (memcmp(g_bt_scan_ctx.devices[i].addr, bd_addr->addr, BD_ADDR_LEN) == 0) {
+                if (g_bt_scan_ctx.devices[i].is_new_device) {
+                    is_new_device = true;
+                    g_bt_scan_ctx.devices[i].is_new_device = false; // 只打印一次
+                }
+                break;
+            }
+        }
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+
+        printf(">>>检测到蓝牙设备:MAC=%02X:%02X:%02X:%02X:%02X:%02X,RSSI=%ddBm\r\n",
+               bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
+               bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0],
+               rssi);
+
+        if (is_new_device) {
+            printf(">>>新蓝牙设备:MAC=%02X:%02X:%02X:%02X:%02X:%02X,RSSI=%ddBm(已检测到%u个不同设备)\r\n",
+                   bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
+                   bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0],
+                   rssi, (unsigned int)unique_count);
+        }
+
+        // 保留原逻辑:RSSI变化超过阈值时也可额外打印(现在不再需要,避免重复)
         // 检查RSSI是否有显著变化(只在新设备或RSSI变化超过5dBm时打印)
         MediaMutexLock(g_bt_scan_ctx.mutex);
         bool should_print = false;
@@ -241,12 +280,7 @@ static void BtScanReadRemoteRssiCallback(const bd_addr_t *bd_addr, int rssi, int
         }
         MediaMutexUnLock(g_bt_scan_ctx.mutex);
         
-        if (should_print) {
-            printf("fac:[BtScan] Device: %02X:%02X:%02X:%02X:%02X:%02X, RSSI=%d dBm\r\n",
-                  bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
-                  bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0],
-                  rssi);
-        }
+        (void)should_print;
         
         // 更新设备RSSI
         BtScanAddOrUpdateDevice(bd_addr, rssi, NULL, false);