源码解析 · Momenta iOS · 需求 R2

相机「激活」功能全链路解析
与「去掉激活」的最小改动方案(含改动前后逻辑对照)

对象 momenta/ios @ develop(13578ebf,2026-09-21) 固件 sd2402/qcs8550/leaptic_app @ release-1.4.0.0 平台 iOS 15.0+ / Swift 证据 本轮实际读取的源码(行号可核) 报告日期 2026-10-14
目录
  1. 结论先行
  2. 三个「激活状态」与状态机
  3. 激活流程逐步拆解(含代码)
  4. 谁在消费「已激活」——门禁清单
  5. 涉及文件清单
  6. 固件侧结论(为什么不用改)
  7. 改动方法(逐点 before / after)
  8. 改动后的逻辑(时序对照)
  9. 影响面、风险与验证清单
  10. 判断与待确认

1 · 结论先行

「激活」不是一道 UI 弹窗,而是三件事叠加:服务端登记 + 三重前置校验(移动网络 / 已登录 / 固件版本)+ 设备侧两条指令(activateWIFI、sendUserId),并以 PTCameraModel.isActivated 作为全 App(含 Flutter)的门禁变量。

因此「去掉激活」的正确做法不是删 UI,而是把这个门禁变量恒置为真、并把两个服务端调用短路掉;设备侧两条指令必须保留(用内置账号下发),否则相机的绑定态(is_bind/is_active)会与现状不一致。

改动面:新增 1 个开关文件(约 30 行)+ 4 处短路(合计不到 60 行 diff);不动激活 UI 文件、不动埋点、不动 Flutter/C++、不动固件。

问题结论
激活必过吗?对「拍摄/预览」是硬门禁;Ring 设备被显式豁免(PTCameraSearchView.swift:953「Ring 设备不走激活流程」)
去掉后相机还能拍吗?能。固件侧 is_active/is_activated 只上报、不作任何功能门禁(详见第 6 节)
需要登录吗?改动后不再需要——激活是唯一「未登录就无法继续」的登录硬门槛(+Activation.swift:23 + PTCameraSearchView.swift:979)
能保留旧行为吗?可以:建议用编译宏 PT_ACTIVATION_BYPASS,正式线默认关闭,回滚 = 关开关

2 · 三个「激活状态」与状态机

排查/改造激活时最容易踩的坑:代码里存在三份互相独立的「是否已激活」,日志里必须区分打印,判断也必须按「或/与」关系组合。

状态来源代码位置特点
服务端激活态GET /photon-server/api/v1/device/isActivated/{sn}PTDeviceNetWorkService.swift:250(URL)、:729(查询)需网络 + 缓存(UserDefaults,key 前缀 + SN);失败时回落设备端状态
设备端激活态相机 BLE 上报 is_activated → PTCameraModel.isActivatedPTCameraModel.swift:400(属性)、:1454(写入)@Published,是全 App 门禁的实际消费变量
蓝牙配置激活态WiFi 配置阶段 BLE 下发的 is_activatedPTBluetoothTypes.swift:411、读点 PTCameraConnectionFlowManager.swift:101用于判断「服务端已激活但设备端没激活」的错配场景

连接流程状态机(PTCameraConnectMode)

enum PTCameraConnectMode: Equatable, Hashable {
    case empty
    case searching
    case connect(PTCameraConnectStep)
    case connectCompleted(PTCameraConnectError?)
    case activateState(PTCameraActivateState)   // ← 激活态独立成一档
    case setPwd(passwordType)
    case setPwdCompleted
    case upgradeSuccess
    case upgradeFail
    case locked
}

enum PTCameraActivateState: Equatable, Hashable {
    case activating
    case success
    case failed(PTCameraActivateFailure)
}

源码:Momenta/Biz/Camera/Connection/Definitions/PTCameraConnectDefines.swift · 466 行(枚举在 250 行与 365 行)

失败类型(10 种,决定 UI 分流)

enum PTCameraActivateFailedType {
    case networkUnavailable                        // 网络不可用
    case notLoggedIn                               // 未登录 → 弹强制登录层
    case activationFailed                           // 服务端业务失败
    case firmwareVersionInvalid                     // 四路固件版本空/不一致 → 引导升级
    case firmwareVersionInvalidNetworkUnavailable
    case firmwareVersionInvalidAndLowBattery
    case deviceAlreadyActivated                     // 服务端已激活(错误码 500 + "已激活")
    case snEmpty                                    // SN 未同步完成
    case activateWIFIFailed
    case other, unknown
}

源码:PTCameraConnectDefines.swift:376-388(shouldAccumulateFailureCount 决定是否计入「激活失败次数」提示)

3 · 激活流程逐步拆解(含代码)

3.1四个入口

入口触发条件落点
A 连接搜索页(主入口)连接成功页点「激活」/ 激活失败点「重试」/ 升级成功后点「激活」PTCameraSearchView.swift:733 / 784 / 816 → PTCameraSearchViewModel.swift:176 → 策略 activateDevice()
B 连接成功后自动检查WiFi 连接成功事件(全局 WiFi delegate,App 生命周期常驻)PTCameraConnectionFlowManager → checkFirmwareUpgrade → checkActivationStatus → performActivationStatusCheck
C 连 WiFi 前的预查连接状态机进入 .connect(.tryToConnectWifi)PTCameraSearchStrategySD2402.swift:139-158:若 !isActivated 就先查服务端激活态,失败直接判 .failed(.networkUnavailable)
D 静默同步激活服务端已激活但设备端/蓝牙配置未激活PTSilentActivationSyncCoordinator(同 PTCameraConnectionFlowManager.swift 内,第 26 行起):每 3 秒重试 activateWIFI,成功则 setActivatedManually(true)

3.2主流程:activateDevice()

四个前置检查按顺序短路,任一失败即进入 .activateState(.failed):

func activateDevice() {
    trackActivationStartEvent()
    updateConnectMode(.activateState(.activating))

    guard PTNetworkReachability.shared.isMobileDataAvailable else { // ① 网络(注意:判的是"移动数据")
        updateConnectMode(.activateState(.failed(...networkUnavailable...))); return }
    guard PTUserDataManager.shared.isLoggedIn else {              // ② 登录
        updateConnectMode(.activateState(.failed(...notLoggedIn...))); return }
    guard !sn.isEmpty else { ...snEmpty...; return }                       // ③ 设备 SN
    if isActivationFirmwareVersionInvalid() { ...引导升级...; return }         // ④ 四路固件版本一致且不在强制升级清单

    // —— 服务端:先查后激活 ——
    let (isSuccess, isActivated, error) = await networkInterceptor.queryServiceActivateStatus()
    if !isActivated {
        let (ok, err) = await networkInterceptor.activateDevice(sn, mac: nil)   // POST /activate/activateDevice
        // 特殊分支:错误码 500 且 message 含"已激活" → deviceAlreadyActivated
    }
    // —— 设备侧:绑定 + 激活(这两条决定了相机内部状态)——
    let (_, isCameraLocked, isBindUser) = await userInterceptor.sendUserId(PTUserDataManager.shared.getAccountId())
    PTCameraModel.shared.isbindUser = isBindUser; PTCameraModel.shared.isCameraLocked = isCameraLocked

    if !PTCameraModel.shared.isActivated {
        let (ok, err) = await systemInterceptor.activateWIFI(PTUserDataManager.shared.getAccountId())
        // 设备回 "camera is already activated,do not need to activate again" 时按成功处理
    }
    PTCameraModel.shared.cacheServiceActivatedStatus(true)
    updateConnectMode(.activateState(.success)); trackActivationSuccessEvent()
}

源码:Momenta/Biz/Camera/Connection/Strategy/PTCameraSearchStrategySD2402+Activation.swift · 312 行(主流程 12-190 行)

3.3网络层:两个服务端调用

// ① 提交激活:PTDeviceNetWorkService.swift:427
func activateDevice(sn: String, mac: String? = nil, completion: ...) {
    let token = userDataManager.getUserToken()
    guard !token.isEmpty else { completion(.failure(.invalidToken)); return }   // 需要登录 token
    httpService.post(activateDeviceUrl, parameters: params, headers: jsonHeaders(withToken: token)) { ... }
}

// ② 查询激活态:PTDeviceNetWorkService.swift:729(含三级回落)
func queryServiceActivateStatus(completion: @escaping (Bool, String?) -> Void) {
    if !PTNetworkReachability.shared.hasInternetAccess {        // 无网 → 本地缓存 → 设备端状态 → toast
        if let cached = PTCameraModel.shared.cachedServiceActivatedStatus() { completion(cached, nil) }
        else if !PTCameraModel.shared.currentSyncDeviceInfo.info.sn.isEmpty { completion(PTCameraModel.shared.isActivated, nil) }
        else { completion(false, "当前网络异常,请稍后重试。") }
        return
    }
    let sn = resolveActivationQuerySN()                            // SN 未同步完成则不发请求(防 404 误报)
    queryActivateStatus(sn: sn) { ... PTCameraModel.shared.cacheServiceActivatedStatus(isActivated) ... }
}

源码:Momenta/DeviceManager/NetWork/PTDeviceNetWorkService.swift · 786 行(URL 常量 236-250 行)

3.4连接成功后的分流(决定「能不能进拍摄页」)

func performActivationStatusCheck(completion: @escaping (PTCameraFirmwareActivationCheckResult) -> Void) {
    PTDeviceNetWorkService.shared.queryServiceActivateStatus { isActivate, errorMessage in
        if let errorMessage { ...showToast(errorMessage); completion(.flowHandled); return }
        let mismatch = !PTCameraModel.shared.isActivated || (bleActivated == false)
        if isActivate {
            if mismatch, !PTUserDataManager.shared.isLoggedIn { completion(.activationRequired); return }  // 强制登录
            if mismatch { silentActivationSyncCoordinator?.startIfNeeded() }                            // 静默补激活
            completion(.readyForCapture)                                                                       // ← 放行
        } else {
            completion(.activationRequired)   // ← 未激活:弹 PTCameraSearchView(.connectCompleted(nil))
        }
    }
}

源码:Momenta/Biz/Camera/Connection/Managers/PTCameraConnectionFlowManager.swift · 551 行(分流结果枚举在同文件 13-22 行)

4 · 谁在消费「已激活」——门禁清单

位置判断行为影响改动后是否需要动
PTCameraConnectionFlowManager.swift:317isActivate(服务端)readyForCapture vs activationRequired(弹激活页)短路查询后自然放行
PTCameraConnectionFlowManager.swift:53!PTCameraModel.isActivated 才启动静默激活同步不再启动重试循环否(恒真即不启动)
PTCameraSearchView.swift:977-990isActivated(服务端查询结果)已激活 → navigateAfterCameraConnected();未激活+未登录 → 强制登录层;未激活 → 显示激活界面否(短路后走"已激活"分支)
PTCameraConnectionFlowManager.swift:241needUpgrade && !isActivated && isRecoveryBlockedFirmwareVersion()弹「未激活先升级」PTUnActivateView否(恒真后不可达)
PTMomentDataManager.swift:123isCameraConnected && isActivatedMoment/相册是否纳入相机素材否(恒真后自动纳入,属预期改善)
PTCameraRouter.swift:92、PTFlutterCameraBridge.swift:942isActivated && !isForcedUpgrade && !isInUpgradeFlow智慧感知「开启预览/拍摄」快速放行(跳过每次联网核查)否
PTFlutterDevicePayload.swift:243/284把 isActivated 下发给 FlutterFlutter 页面的设备状态展示/分支否(恒真后 Flutter 侧也认为已激活)
PTCameraSearchStrategySD2402.swift:139-158!PTCameraModel.isActivated 时预查服务端连 WiFi 前多一次网络往返,失败即报"网络异常"建议改(去掉这次预查)

一个现成的先例(本轮发现):仓库里已有调试开关 PTDebugToolsManager.shared.isShowActivate(默认 true,可在 Debug 页关闭)。

// PTCameraSearchView.swift:959
if PTDebugToolsManager.shared.isShowActivate == false {
    logInfoEx("PTCameraSearchView:相机连接成功,调试工具设置为不显示激活界面,按相册权限决定去向")
    navigateAfterCameraConnected()
    return
}

源码:Momenta/Debug/PTDebugToolsManager.swift:33(内存变量,非持久化;读取点仅 PTCameraSearchView.swift:959)

它只跳过「激活界面展示」,并不阻止服务端查询/激活调用,也没有让 PTCameraModel.isActivated 变真(下游门禁仍按未激活走)。不要拿它当开关用,但可以佐证:把激活入口旁路掉在产品上是既有形态。

5 · 涉及文件清单

文件行数角色本次改动
Biz/Camera/Connection/Strategy/PTCameraSearchStrategySD2402+Activation.swift312激活主流程(预检 + 云端 + 设备侧)改 1 顶部短路
DeviceManager/NetWork/PTDeviceNetWorkService.swift786两个服务端接口(查询/提交)改 2 查询短路
Biz/Capture/Models/PTCameraModel.swift2663门禁变量 isActivated 的写入点改 3 写入恒真
Biz/Camera/Connection/Strategy/PTCameraSearchStrategySD2402.swift475连接状态机、连 WiFi 前预查、模式转换改 4 预查短路
新增 Biz/Camera/Activation/PTActivationBypass.swift(约 30 行,开关集中地)
Biz/Camera/Connection/Managers/PTCameraConnectionFlowManager.swift551连接成功分流 + 静默激活同步不改(上游短路后自然走 readyForCapture)
Biz/Camera/Connection/Views/PTCameraSearchView.swift1307激活 UI 状态映射与三个激活按钮回调不改(失败分支不可达)
Biz/Camera/Connection/Definitions/PTCameraConnectDefines.swift466PTCameraActivateState/失败类型枚举不改(保留枚举,避免 diff 扩散)
Biz/Camera/Activation/*、Connection/Views/PTCameraActivationFailed*、FirmwareUpgrade/Views/UnActivate/*—激活失败/未登录/未激活先升级 UI(约 10 个视图 + 1 个 ViewModel)不删(保留但不可达)
Biz/Camera/Connection/Views/PTCameraSearchViewModel.swift—VM 转发 + 「登录完成 → 等激活终结态」链式回调不改
Debug/PTDebugToolsManager.swift—isShowActivate 调试开关不改(与本次开关无关,勿复用)

6 · 固件侧结论:为什么不用改

固件(sd2402/qcs8550/leaptic_app @ release-1.4.0.0,实际构建入口在 sd2402/manifest:leaptic_app → qcs8550/src/quectel-app/Zhuimi_Application/src/)里,激活相关字段是纯状态上报:

// mobile/src/UserCenter.cpp:344-384(启动归一化 + 上报口径)
if (is_actived == 0) {   // 工厂态:插入/重置默认行
    INSERT OR REPLACE INTO UserInfo(ID, userid, password, is_bind, is_first_connect, is_lock, is_active, lock_time)
      VALUES(1, 'default_user', '', 0, 1, 0, 0, '');
}
...
LOG_INFO("userinfo.is_active is :%d", userinfo.is_active);
if (userinfo.is_active == 0 || userinfo.is_active == 1) {
    CameraSettingInfo::getInstance().is_activated = true;   // ← 工厂态(0)也按"已激活"上报
} else {
    CameraSettingInfo::getInstance().is_activated = false;
}
// mobile/src/Subtask.cpp:103-182(App 下发 userId 即完成"激活")
json SubtaskHandler::handle_activate(json request) {
    ...
    userinfo.is_active = 1;
    persistActivateWithRetry(userinfo, ACTIVATE_IMMEDIATE_RETRY_MAX, dbError);  // 只落本地 sqlite
    fdclient->send_active_status(userinfo.is_active, &msg_bt);                 // 通知 App
    CameraSettingInfo::getInstance().is_activated = true;
    CAMERA_ACTIVATE(1, app_ver.c_str());                                       // 埋点
}

证据结论:全仓检索 is_activated / is_active 的出现点只有「赋值、落库、上报、日志」四类,没有任何 if (is_activated) 形式的功能门禁;handle_activate 全程不访问云端。⇒ 不去下发 activate,相机依然按已激活状态工作,拍摄、预览、存储、Wi-Fi 配网均不受影响。

附:固件里唯一与云相关的默认地址是 mobile/src/cloud/https_client.cpp:1351-1353 的 https://photon-test.leaptic.tech/photon-server(云存功能的控制面基址,凭据由 App 下发),与激活无关,属需求 R1 的范围。

7 · 改动方法(逐点 before / after)

总体思路:一个开关 + 四处短路 + 保留设备侧指令。开关形态建议编译期宏(正式线零影响、可一键回滚),并提供运行时覆盖便于真机调试。

7.0新增文件:Momenta/Biz/Camera/Activation/PTActivationBypass.swift

import Foundation

/// 去激活开关:开启后 App 不再要求「相机激活」,也不访问服务端激活接口。
/// 编译期:Build Settings 里为对应 Configuration 增加 SWIFT_ACTIVE_COMPILATION_CONDITIONS = PT_ACTIVATION_BYPASS
/// 运行时:启动参数 -PTActivationBypass 或 UserDefaults.standard.bool(forKey: "PTActivationBypass")(便于真机开关调试)
enum PTActivationBypass {
    static var isEnabled: Bool {
        #if PT_ACTIVATION_BYPASS
        return true
        #else
        return ProcessInfo.processInfo.arguments.contains("-PTActivationBypass")
            || UserDefaults.standard.bool(forKey: "PTActivationBypass")
        #endif
    }
    /// 设备侧激活使用的账号(内置用户),见 R1 的本地账号 fixture
    static var builtinAccountId: String {
        PTUserDataManager.shared.getAccountId()   // 未登录时由内置用户提供
    }
}

7.1改动一:激活主流程顶部短路(+Activation.swift:12)

跳过 4 项前置检查与两个服务端调用,但保留设备侧 sendUserId + activateWIFI,让相机内部绑定态与现状一致。

// BEFORE
func activateDevice() {
    trackActivationStartEvent()
    updateConnectMode(.activateState(.activating))
    guard PTNetworkReachability.shared.isMobileDataAvailable else { ... }
    guard PTUserDataManager.shared.isLoggedIn else { ... }
    ...
}

// AFTER
func activateDevice() {
    if PTActivationBypass.isEnabled {
        performBypassActivation()      // 新增:直接走成功态 + 设备侧对齐
        return
    }
    trackActivationStartEvent()
    ...
}

/// 去激活模式:不查云端、不提交激活,只做设备侧绑定/激活
private func performBypassActivation() {
    updateConnectMode(.activateState(.activating))
    Task { [weak self] in guard let self else { return }
        let account = PTActivationBypass.builtinAccountId
        let userInterceptor = PTSDKFactory.createUserCommand()
        let systemInterceptor = PTSDKFactory.createSystemCommand()
        if let (ok, locked, bound) = try? await userInterceptor.sendUserId(account), ok {
            await MainActor.run {
                PTCameraModel.shared.isbindUser = bound
                PTCameraModel.shared.isCameraLocked = locked
            }
        }
        _ = try? await systemInterceptor.activateWIFI(account)   // 相机侧 is_active=1 / is_bind=1
        await MainActor.run {
            PTCameraModel.shared.setActivatedManually(true)
            self.updateConnectMode(.activateState(.success))
        }
    }
}

7.2改动二:服务端激活状态查询短路(PTDeviceNetWorkService.swift:729)

// BEFORE
func queryServiceActivateStatus(completion: @escaping (Bool, String?) -> Void) {
    logInfoEx("查询服务器端设备是否已激活,是否有网络:\(PTNetworkReachability.shared.hasInternetAccess)")
    if !PTNetworkReachability.shared.hasInternetAccess { ... }
    ...

// AFTER(第 1 行插入)
func queryServiceActivateStatus(completion: @escaping (Bool, String?) -> Void) {
    if PTActivationBypass.isEnabled {            // 去激活:不再请求 /device/isActivated/
        completion(true, nil)
        return
    }
    logInfoEx(...)
}

连带效果:`/device/isActivated/{sn}` 不再被调用;无网时的「缓存 / 设备态 / toast」三级回落全部不再触发(也就不再有"网络异常"toast)。

7.3改动三:门禁变量恒真(PTCameraModel.swift:1454)

// BEFORE(设备信息同步时按 BLE 上报写入)
self.isActivated = info.isActivated

// AFTER
self.isActivated = PTActivationBypass.isEnabled ? true : info.isActivated

这一处是全 App 门禁的总闸:PTCameraRouter/PTFlutterCameraBridge(快速预览放行)、PTMomentDataManager(相机素材纳入)、PTCameraConnectionFlowManager(静默同步不再启动)、以及下发给 Flutter 的 isActivated 字段,全部随之变为「已激活」。PTCameraModel.setActivatedManually(_:)(同文件 :1511)是既有的手动置位接口,无需新增机制。

7.4改动四:连 WiFi 前的预查短路(PTCameraSearchStrategySD2402.swift:139)

// BEFORE
case .connect(.tryToConnectWifi):
    if !PTCameraModel.shared.isActivated {
        PTDeviceNetWorkService.shared.queryServiceActivateStatus { isActivated, errorMessage in
            // 查询失败 → 直接把连接流打成 .activateState(.failed(.networkUnavailable))
        }
    }

// AFTER(改动三之后其实已不可达,仍建议显式短路,避免"隐式依赖")
case .connect(.tryToConnectWifi):
    if !PTActivationBypass.isEnabled, !PTCameraModel.shared.isActivated { ...原逻辑... }

7.5不改的部分(保持最小 diff)

另一种口径(若"所有版本都去掉"):把上述四处短路里的开关判断去掉,直接按"恒真/短路"实现,并顺手把激活 UI 文件删除、把 /activate/activateDevice 与 /device/isActivated/ 常量一并清理。代价是需要回归所有连接路径(含 Ring/GoPro/座舱、升级流程、账号切换),且无法"关开关回到旧行为"。

8 · 改动后的逻辑(时序对照)

改动前(现状) BLE 扫描/配对 → Wi-Fi(AP) 配网 → WebSocket 就绪 → 固件升级检查(needUpgrade?) ├─ 未激活 + 命中强制升级清单 → PTUnActivateView(未激活先升级) └─ 否 → 继续 → queryServiceActivateStatus() 需网络;无网走缓存/设备态/toast → [未激活] activateDevice(sn) 需登录 token → POST /activate/activateDevice → sendUserId(accountId) → isBindUser / isCameraLocked → activateWIFI(accountId)(仅当设备端未激活) → cacheServiceActivatedStatus(true) → .activateState(.success) → readyForCapture → 进拍摄/预览 失败分支:未登录→强制登录层;网络→toast;已激活→toast;版本异常→引导升级
改动后(去激活模式) BLE 扫描/配对 → Wi-Fi(AP) 配网 → WebSocket 就绪 → 固件升级检查(保留:升级本身仍是真实需求) → isActivated 恒为 true(写入点被短路) → queryServiceActivateStatus() 直接返回 (true, nil),零网络请求 → 不调用 /activate/activateDevice → sendUserId(内置账号) → isBindUser / isCameraLocked(保留,设备侧行为不变) → activateWIFI(内置账号)(保留 → 相机 is_active=1 / is_bind=1) → readyForCapture → 进拍摄/预览 不再出现:激活界面、激活失败弹窗、强制登录层、"未激活先升级"、网络异常 toast

对照要点:网络请求少 1~2 次(每次连接成功省一次 /isActivated/,未激活时再省一次 /activate/activateDevice);设备侧指令一条不少,因此相机内部状态、后续"忘记密码/解绑/换绑"等依赖绑定态的流程行为与现状一致。

9 · 影响面、风险与验证清单

项说明风险等级
服务端不再登记设备云端看不到该设备(与 R2 目标一致),云相册/订阅等本已不可用低(预期)
多账号切换固定使用内置账号:换账号后相机 is_bind 仍指向内置账号,服务端侧绑定关系不再更新中(取决于是否还需要换绑)
未激活先升级链路PTUnActivateView 不再可达;若某台相机固件过旧,将不再被这条链路引导升级中(需确认是否保留替代入口:设置页的固件升级仍在)
激活埋点断链PTActivationAnalytics 的激活漏斗不再上报;若后台报表依赖该漏斗会出现数据缺口低(须知会数据方)
登录不再必需激活是"未登录无法继续"的唯一硬门槛;去掉后 App 能完全在未登录态使用设备功能低(符合 R1 方向)
开关形态编译宏默认关闭 ⇒ 正式线与旧包行为一致;忘记开开关的表现=与现状相同低
Flutter / C++ / 固件接口未变(isActivated 只是值变真),无需联动改动低

验证清单(建议按序执行)

  1. 编译:开启 PT_ACTIVATION_BYPASS 的配置能出包;关闭时 diff 行为为零(同一提交两个产物对比)。
  2. 模拟器:进连接页不发起 /isActivated/、/activate/activateDevice(抓包或日志确认)。
  3. 真机·未激活相机:连接成功后直接进预览/拍摄;不出现激活页/强制登录层;日志应出现 [PTCameraConnectionFlowManager] 相机已激活,可以跳转拍摄页面。
  4. 设备侧副作用:连上后相机设置页能看到绑定账号=内置账号;is_bind/is_active 为真(BLE 上报或调试页确认)。
  5. 存量已激活相机:行为与改动前一致(不回归)。
  6. 断网场景:飞行模式/无网下连接相机,不再出现"网络异常"toast,也不卡在激活页。
  7. Moment/相册:连接相机后 PTMomentDataManager 是否纳入相机素材(预期纳入)。

10 · 判断与待确认

✔ 支持「4 处短路」方案的理由
  • 唯一门禁变量 PTCameraModel.isActivated 有既有手动置位接口,短路点集中、可审计
  • 服务端两个接口与 UI 层完全解耦,短路后 UI 分支自然不可达,diff 极小、可一键回滚
  • 设备侧指令保留 ⇒ 相机内部状态不变,避免"忘记密码/解绑"这类连带回归
  • 固件零改动(有源码证据,不是推断)
⚠ 保留意见
  • "未激活先升级"链路消失后,旧固件相机的升级引导需有替代路径(设置页升级仍在,但少了强制入口)
  • 内置账号是固定 accountId,一旦将来要恢复云功能,需要一次数据/绑定迁移设计
  • 编译宏 + 运行时覆盖双层开关,测试时容易出现"以为开了其实没开"——建议在启动日志里打印开关状态
  • 本轮未做真机验证(构建机环境尚未就绪),第 9 节清单必须在真机上过一遍再发版

判断

去激活是纯 App 侧、可逆、低耦合的改动,建议按「编译宏开关 + 4 处短路 + 保留设备侧指令」实施;不建议删 UI 文件或直接改逻辑(会同时失去回滚能力与最小回归面)。真正的风险不在代码改动,而在两处业务口径:①未激活先升级链路是否还需要;②固定内置账号对"换绑/解绑"的长期影响——这两点需要产品侧拍板。

待确认(发版前需要用户决定)

  1. 生效范围:仅离线版(开关控制)还是所有版本(直接改逻辑)?
  2. 未激活先升级:整条去掉,还是保留(新逻辑下不可达)并在设置页提供升级入口?
  3. 埋点:激活漏斗是否需要在去激活模式下换成"本地日志/替代事件"?
  4. 内置账号:确认使用"内置用户"的账号 ID 与昵称(用于相机侧绑定展示)。