博客 / 詳情

返回

Rokid應用實踐:基於AI Glasses 智慧快遞站“解放雙手”的快件錄入歸類助手

一、創意緣起:工作堆積如山,科技順其有序

場景:

下午 2 點的快遞站倉庫,王師傅蹲在堆積如山的快件中,左手抱着一摞包裹,右手緊握掃碼槍對準條碼掃描。他需要頻繁彎腰將快件放入對應貨架格,汗水浸濕後背工裝。

當 Rokid AI Glasses 智能眼鏡遇見智慧物流

在快遞業務量持續增長的今天,快遞站工作人員面臨着巨大的分揀壓力。傳統的快件錄入需要反覆查看面單、手動輸入信息、分類擺放,整個過程耗時耗力且容易出錯。而 Rokid AI Glasses 的出現,為這一場景帶來了新的解決方案。

image.png

本文將詳細介紹如何利用 Rokid CXR-M(移動端)和 CXR-S(眼鏡端)SDK,構建一個解放雙手的快件錄入歸類助手,實現"所見即所得"的智能分揀體驗。

二、系統架構設計

架構總覽

系統採用 “眼鏡端採集 + 手機端協同 + 雲端同步” 的三層架構,核心依賴 Rokid SDK 實現設備交互與數據流轉:

• 終端層(CXR-S AI眼鏡)

作為“感知與輸出終端”,負責快件條碼識別、語音指令接收、操作指引顯示,基於 CXR-S SDK 實現本地 AI 識別與狀態監聽

• 業務邏輯層(CXR-M移動設備)

通過 CXR-M SDK 實現設備連接管理、數據緩存、雲端通信,承接眼鏡端採集的數據並同步至管理系統。

• 雲端層(數據服務)

提供快件信息校驗、歸類規則存儲、數據統計分析功能,通過 API 與手機端實時交互。

image.png

核心技術依賴

  • 設備連接:基於 CXR-M SDK 的藍牙掃描、Wi-Fi P2P 連接能力,保障設備穩定通信。
  • 數據採集:藉助眼鏡端相機接口(CXR-M SDK openGlassCamera)實現條碼掃描,語音識別接口接收操作指令。
  • 交互展示:通過提詞器場景(configWordTipsText)顯示快件信息與歸類指引,自定義界面場景展示實時數據。
  • 數據同步:利用 Wi-Fi P2P 高速傳輸能力(startSync)實現快件圖片、信息的即時同步。

三、關鍵功能技術實現

(一).眼鏡端(CXR-S SDK)集成配置

1.環境準備與依賴導入

配置 Maven 倉庫

在項目settings.gradle.kts中添加 Rokid Maven 倉庫,確保 SDK 包正常拉取:

pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        // 添加Rokid Maven倉庫
        maven {
            url = uri("https://maven.rokid.com/repository/maven-public/")
        }
        mavenCentral()
    }
}
rootProject.name = "ExpressSorting_Glasses"
include(":app")
導入 CXR-S SDK 依賴

在app/build.gradle.kts中添加 SDK 依賴,設置最小 SDK 版本≥28:

android {
    namespace = "com.rokid.expresssorting.glasses"
    compileSdk = 34
 
    defaultConfig {
        applicationId = "com.rokid.expresssorting.glasses"
        minSdk = 28 // 必須≥28
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
 
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
 
    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
 
dependencies {
    // 基礎依賴
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
 
    // 導入CXR-S SDK
    implementation("com.rokid.cxr:cxr-service-bridge:1.0-20250519.061355-45")
    // 條碼解析庫(本地識別)
    implementation("com.google.zxing:core:3.5.1")
}

2.眼鏡端核心初始化(CXRServiceBridge)

實現 SDK 核心類CXRServiceBridge的初始化,配置連接狀態監聽與消息訂閲,支撐條碼識別與指令交互:

import android.app.Application
import com.rokid.cxr.CXRServiceBridge
import com.rokid.cxr.Caps
import android.util.Log
 
class ExpressSortingApp : Application() {
    companion object {
        const val TAG = "ExpressSorting_Glasses"
        lateinit var cxrBridge: CXRServiceBridge
            private set
    }
 
    override fun onCreate() {
        super.onCreate()
        // 1. 初始化CXRServiceBridge(必須在主線程初始化)
        cxrBridge = CXRServiceBridge()
        // 2. 設置連接狀態監聽(監聽手機端連接)
        initStatusListener()
        // 3. 訂閲手機端指令消息(如條碼識別請求、分揀指引更新)
        subscribeMobileCommands()
    }
 
    /**
     * 初始化連接狀態監聽
     */
    private fun initStatusListener() {
        cxrBridge.setStatusListener(object : CXRServiceBridge.StatusListener {
            override fun onConnected(name: String, type: Int) {
                Log.d(TAG, "已連接手機設備:$name,設備類型:${getDeviceTypeDesc(type)}")
                // 連接成功後,初始化本地相機參數(為條碼掃描做準備)
                initLocalCameraParams()
            }
 
            override fun onDisconnected() {
                Log.d(TAG, "與手機設備斷開連接")
                // 斷開連接後,釋放相機資源
                releaseCameraResources()
            }
 
            override fun onARTCStatus(health: Float, reset: Boolean) {
                Log.d(TAG, "ARTC連接健康度:${(health * 100).toInt()}%,是否重置:$reset")
            }
        })
    }
 
    /**
     * 訂閲手機端指令消息(普通消息訂閲模式)
     */
    private fun subscribeMobileCommands() {
        // 訂閲"條碼識別請求"指令
        val scanCmdSubscribeResult = cxrBridge.subscribe("mobile_cmd_scan_barcode", 
            object : CXRServiceBridge.MsgCallback {
                override fun onReceive(name: String, args: Caps, value: ByteArray?) {
                    Log.d(TAG, "收到手機端條碼識別請求:$name")
                    // 解析請求參數(如分辨率、壓縮質量)
                    val width = if (args.size() > 0) args.at(0).getInt() else 1920
                    val height = if (args.size() > 1) args.at(1).getInt() else 1080
                    val quality = if (args.size() > 2) args.at(2).getInt() else 80
                    // 執行本地條碼掃描
                    LocalBarcodeScanner.scan(width, height, quality)
                }
            })
        if (scanCmdSubscribeResult == 0) {
            Log.d(TAG, "條碼識別請求指令訂閲成功")
        } else {
            Log.e(TAG, "條碼識別請求指令訂閲失敗,錯誤碼:$scanCmdSubscribeResult")
        }
 
        // 訂閲"分揀指引更新"指令
        val guideCmdSubscribeResult = cxrBridge.subscribe("mobile_cmd_update_guide",
            object : CXRServiceBridge.MsgCallback {
                override fun onReceive(name: String, args: Caps, value: ByteArray?) {
                    Log.d(TAG, "收到手機端分揀指引更新:$name")
                    // 解析指引信息並顯示(提詞器場景)
                    if (args.size() > 0) {
                        val guideText = args.at(0).getString()
                        GuideDisplayManager.showGuide(guideText)
                    }
                }
            })
        if (guideCmdSubscribeResult == 0) {
            Log.d(TAG, "分揀指引更新指令訂閲成功")
        } else {
            Log.e(TAG, "分揀指引更新指令訂閲失敗,錯誤碼:$guideCmdSubscribeResult")
        }
    }
 
    /**
     * 初始化本地相機參數(通過Caps寫入配置)
     */
    private fun initLocalCameraParams() {
        val cameraConfig = Caps()
        cameraConfig.write("init_camera_params") // 指令標識
        cameraConfig.writeInt32(1920) // 默認寬度
        cameraConfig.writeInt32(1080) // 默認高度
        cameraConfig.writeInt32(80) // 默認質量
        // 發送配置到底層(通過sendMessage接口)
        val sendResult = cxrBridge.sendMessage("glasses_cmd_init_camera", cameraConfig)
        if (sendResult != 0) {
            Log.e(TAG, "相機參數初始化失敗,錯誤碼:$sendResult")
        }
    }
 
    /**
     * 釋放相機資源
     */
    private fun releaseCameraResources() {
        val releaseCmd = Caps()
        releaseCmd.write("release_camera")
        val sendResult = cxrBridge.sendMessage("glasses_cmd_release_camera", releaseCmd)
        if (sendResult != 0) {
            Log.e(TAG, "相機資源釋放失敗,錯誤碼:$sendResult")
        }
    }
 
    /**
     * 解析設備類型
     */
    private fun getDeviceTypeDesc(type: Int): String {
        return when (type) {
            CXRServiceBridge.StatusListener.DEVICE_TYPE_ANDROID -> "Android手機"
            CXRServiceBridge.StatusListener.DEVICE_TYPE_IOS -> "iPhone"
            else -> "未知設備"
        }
    }
}

3 .眼鏡端本地條碼識別與指引顯示

基於 CXR-S SDK 的Caps數據結構與相機接口,實現本地條碼掃描、結果回傳與分揀指引顯示:

// 本地條碼掃描工具類
import com.rokid.cxr.CXRServiceBridge
import com.rokid.cxr.Caps
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.google.zxing.BarcodeFormat
import com.google.zxing.DecodeHintType
import com.google.zxing.MultiFormatReader
import com.google.zxing.Result
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.BinaryBitmap
import com.google.zxing.RGBLuminanceSource
import java.util.EnumMap
import java.io.ByteArrayOutputStream
 
object LocalBarcodeScanner {
    private const val TAG = "LocalBarcodeScanner"
    private val cxrBridge = ExpressSortingApp.cxrBridge
 
    /**
     * 執行本地條碼掃描
     * @param width 掃描分辨率寬度
     * @param height 掃描分辨率高度
     * @param quality 圖像壓縮質量(0-100)
     */
    fun scan(width: Int, height: Int, quality: Int) {
        // 1. 調用本地相機接口獲取條碼圖像(對接眼鏡端硬件相機)
        val barcodeImage = captureBarcodeImage(width, height, quality)
        if (barcodeImage == null) {
            Log.e(TAG, "相機採集圖像失敗")
            sendScanResult(false, "採集失敗", null)
            return
        }
 
        // 2. 解析條碼信息(使用ZXing庫)
        val decodeResult = decodeBarcode(barcodeImage)
        if (decodeResult != null) {
            Log.d(TAG, "本地解析條碼成功:${decodeResult.text}")
            // 3. 回傳識別結果到手機端
            sendScanResult(true, decodeResult.text, barcodeImage)
        } else {
            Log.e(TAG, "本地解析條碼失敗,觸發雲端解析")
            // 4. 本地解析失敗,將圖像回傳手機端發起雲端解析
            sendScanResult(false, "本地解析失敗", barcodeImage)
        }
    }
 
    /**
     * 調用眼鏡端相機採集條碼圖像
     */
    private fun captureBarcodeImage(width: Int, height: Int, quality: Int): ByteArray? {
        // 實際項目需對接眼鏡端相機API,此處模擬採集流程
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val outputStream = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.WEBP, quality, outputStream)
        return outputStream.toByteArray()
    }
 
    /**
     * 解析條碼信息(ZXing實現)
     */
    private fun decodeBarcode(imageData: ByteArray): Result? {
        val options = EnumMap<DecodeHintType, Any>(DecodeHintType::class.java)
        options[DecodeHintType.CHARACTER_SET] = "UTF-8"
        options[DecodeHintType.POSSIBLE_FORMATS] = listOf(
            BarcodeFormat.CODE_128,
            BarcodeFormat.CODE_39,
            BarcodeFormat.EAN_13,
            BarcodeFormat.EAN_8,
            BarcodeFormat.UPC_A
        )
        val reader = MultiFormatReader()
        reader.setHints(options)
 
        try {
            val bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
            val source = RGBLuminanceSource(bitmap.width, bitmap.height, getPixels(bitmap))
            val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
            return reader.decode(binaryBitmap)
        } catch (e: Exception) {
            Log.e(TAG, "條碼解析異常:${e.message}")
            return null
        }
    }
 
    /**
     * 回傳掃描結果到手機端(使用CXR-S SDK的sendMessage接口)
     */
    private fun sendScanResult(success: Boolean, result: String, imageData: ByteArray?) {
        val resultCaps = Caps()
        resultCaps.write(if (success) "scan_success" else "scan_failed") // 狀態標識
        resultCaps.write(result) // 結果文本
        if (imageData != null) {
            resultCaps.write(imageData) // 圖像數據(可選)
        }
 
        // 發送結果到手機端
        val sendResult = cxrBridge.sendMessage("glasses_result_scan", resultCaps, imageData)
        if (sendResult != 0) {
            Log.e(TAG, "結果回傳失敗,錯誤碼:$sendResult")
        }
    }
 
    /**
     * 輔助方法:獲取Bitmap像素數組
     */
    private fun getPixels(bitmap: Bitmap): IntArray {
        val width = bitmap.width
        val height = bitmap.height
        val pixels = IntArray(width * height)
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
        return pixels
    }
}
 
// 分揀指引顯示管理類
import com.rokid.cxr.Caps
import com.rokid.cxr.client.extend.CxrApi
import com.rokid.cxr.client.extend.utils.ValueUtil
 
object GuideDisplayManager {
    private const val TAG = "GuideDisplayManager"
    private val cxrBridge = ExpressSortingApp.cxrBridge
 
    /**
     * 在眼鏡端提詞器顯示分揀指引
     */
    fun showGuide(guideText: String) {
        // 1. 配置提詞器樣式(通過Caps傳遞參數)
        val configCaps = Caps()
        configCaps.write("config_word_tips")
        configCaps.writeFloat(18f) // textSize
        configCaps.writeFloat(4f)  // lineSpace
        configCaps.write("normal") // mode
        configCaps.writeInt32(100) // startPointX
        configCaps.writeInt32(200) // startPointY
        configCaps.writeInt32(800) // width
        configCaps.writeInt32(400) // height
 
        // 發送配置到提詞器場景
        val configResult = cxrBridge.sendMessage("glasses_cmd_config_guide", configCaps)
        if (configResult != 0) {
            Log.e(TAG, "提詞器配置失敗,錯誤碼:$configResult")
            return
        }
 
        // 2. 顯示指引文本
        val textCaps = Caps()
        textCaps.write("show_guide_text")
        textCaps.write(guideText)
        val textResult = cxrBridge.sendMessage("glasses_cmd_show_guide", textCaps)
        if (textResult != 0) {
            Log.e(TAG, "指引文本顯示失敗,錯誤碼:$textResult")
        }
    }
 
    /**
     * 語音播報指引(通過TTS接口)
     */
    fun speakGuide(guideText: String) {
        val ttsCaps = Caps()
        ttsCaps.write("tts_guide")
        ttsCaps.write(guideText)
        val ttsResult = cxrBridge.sendMessage("glasses_cmd_tts", ttsCaps)
        if (ttsResult != 0) {
            Log.e(TAG, "TTS播報失敗,錯誤碼:$ttsResult")
        }
    }

(二)手機端(CXR-M SDK)集成配置

1.環境準備與依賴導入

配置 Maven 倉庫

在settings.gradle.kts中添加 Rokid Maven 倉庫:

pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        // 添加Rokid Maven倉庫
        maven { url = uri("https://maven.rokid.com/repository/maven-public/") }
        google()
        mavenCentral()
    }
}
rootProject.name = "ExpressSorting_Mobile"
include(":app")
導入 CXR-M SDK 依賴與權限配置

在app/build.gradle.kts中添加 SDK 依賴,設置minSdk≥28

android {
    namespace = "com.rokid.expresssorting.mobile"
    compileSdk = 34
 
    defaultConfig {
        applicationId = "com.rokid.expresssorting.mobile"
        minSdk = 28 // 必須≥28
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
 
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
 
    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
 
dependencies {
    // 基礎依賴
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
 
    // 導入CXR-M SDK
    implementation("com.rokid.cxr:client-m:1.0.1-20250812.080117-2")
 
    // SDK依賴的第三方庫(避免版本衝突)
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.squareup.okhttp3:okhttp:4.9.3")
    implementation("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
    implementation("com.squareup.okio:okio:2.8.0")
    implementation("com.google.code.gson:gson:2.10.1")
    implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
 
    // 條碼解析庫(雲端解析備用)
    implementation("com.google.zxing:core:3.5.1")
    // 網絡請求庫(對接雲端API)
    implementation("com.squareup.retrofit2:adapter-rxjava2:2.9.0")
}

2.手機端核心初始化(CxrApi)

實現CxrApi單例初始化,配置藍牙掃描、設備連接與 Wi-Fi P2P 管理:

import android.app.Application
import android.bluetooth.BluetoothDevice
import android.content.Context
import com.rokid.cxr.client.extend.CxrApi
import com.rokid.cxr.client.extend.callbacks.BluetoothStatusCallback
import com.rokid.cxr.client.extend.callbacks.WifiP2PStatusCallback
import com.rokid.cxr.client.extend.utils.ValueUtil
import android.util.Log
 
class ExpressSortingMobileApp : Application() {
    // 1. 全局變量:存儲設備連接信息與狀態
    companion object {
        const val TAG = "ExpressSorting_Mobile" // 日誌標籤
        lateinit var instance: ExpressSortingMobileApp // 應用上下文單例
            private set
        var savedDevice: BluetoothDevice? = null // 已連接的眼鏡設備
        var savedUuid: String? = null // 設備UUID(藍牙連接關鍵參數)
        var savedMac: String? = null // 設備MAC地址(重連關鍵參數)
        var isDeviceConnected = false // 設備連接狀態標記
    }
 
    override fun onCreate() {
        super.onCreate()
        instance = this // 初始化應用上下文
        // 2. 初始化核心模塊:CxrApi、藍牙掃描、數據同步
        initCxrApi() // 初始化CXR-M SDK核心
        BluetoothScanHelper.init(this) // 初始化藍牙掃描工具
        DataSyncManager.init(this) // 初始化數據同步管理器
    }
 
    /**
     * 3. CxrApi單例初始化(SDK核心入口)
     */
    private fun initCxrApi() {
        // 獲取CxrApi單例(SDK全局唯一實例,無需重複創建)
        val cxrApi = CxrApi.getInstance()
        // 打印SDK版本信息(調試用,確認SDK正常加載)
        Log.d(TAG, "CXR-M SDK版本信息:${getSdkVersion()}")
        
        // 後續模塊(藍牙連接、Wi-Fi初始化、消息訂閲)將在此處擴展
    }
 
    /**
     * 輔助方法:獲取SDK版本(從CxrApi內部屬性解析)
     */
    private fun getSdkVersion(): String {
        // 版本信息來自CxrApi源碼定義(見文檔5:CxrApi.txt)
        return "版本名:1.0.1,版本號:101,構建時間:2025-08-12 16:01:17"
    }
}

3 手機端藍牙掃描與雲端交互工具類

配置藍牙連接回調(BluetoothStatusCallback),監聽眼鏡端連接、斷開、失敗狀態。

  • 解析眼鏡端設備信息(UUID、MAC 地址)並緩存,為後續重連提供參數。
  • 實現斷開自動重連邏輯,保障移動場景下連接穩定性。

<!---->

private fun initCxrApi() {
    val cxrApi = CxrApi.getInstance()
    Log.d(TAG, "CXR-M SDK版本信息:${getSdkVersion()}")
 
    // 4. 配置藍牙連接回調:監聽眼鏡端藍牙狀態變化
    cxrApi.setBluetoothStatusCallback(object : BluetoothStatusCallback {
        /**
         * 回調1:獲取眼鏡端設備信息(連接成功後觸發)
         * @param socketUuid:藍牙通信UUID(關鍵連接參數)
         * @param macAddress:設備MAC地址(重連用)
         * @param rokidAccount:Rokid賬號(可選,用於賬號綁定)
         * @param glassesType:眼鏡類型(0=無屏,1=有屏)
         */
        override fun onConnectionInfo(
            socketUuid: String?,
            macAddress: String?,
            rokidAccount: String?,
            glassesType: Int
        ) {
            Log.d(TAG, "獲取眼鏡設備信息:UUID=$socketUuid, MAC=$macAddress, 類型=$glassesType")
            // 緩存設備信息(重連時複用,避免重複掃描)
            savedUuid = socketUuid
            savedMac = macAddress
        }
 
        /**
         * 回調2:藍牙連接成功(可觸發後續Wi-Fi初始化)
         */
        override fun onConnected() {
            Log.d(TAG, "眼鏡端藍牙連接成功")
            isDeviceConnected = true // 更新連接狀態
            initWifiP2P() // 連接成功後,初始化Wi-Fi(用於數據同步)
            subscribeGlassesResults() // 訂閲眼鏡端消息(如條碼識別結果)
        }
 
        /**
         * 回調3:藍牙連接斷開(觸發自動重連)
         */
        override fun onDisconnected() {
            Log.d(TAG, "眼鏡端藍牙連接斷開")
            isDeviceConnected = false // 更新連接狀態
            // 自動重連:複用緩存的設備信息
            savedDevice?.let { device ->
                connectToGlasses(this@ExpressSortingMobileApp, device)
            }
        }
 
        /**
         * 回調4:藍牙連接失敗(打印錯誤原因)
         * @param errorCode:錯誤碼(見ValueUtil.CxrBluetoothErrorCode)
         * - PARAM_INVALID:參數錯誤(如UUID為空)
         * - BLE_CONNECT_FAILED:BLE連接失敗
         * - SOCKET_CONNECT_FAILED:Socket連接失敗
         */
        override fun onFailed(errorCode: ValueUtil.CxrBluetoothErrorCode?) {
            Log.e(TAG, "藍牙連接失敗,錯誤碼:${errorCode?.name}")
            isDeviceConnected = false
        }
    })
}
 
/**
 * 輔助方法:主動連接眼鏡設備(用於首次連接或重連)
 * @param context:應用上下文
 * @param device:目標藍牙設備(從掃描結果獲取)
 */
fun connectToGlasses(context: Context, device: BluetoothDevice) {
    savedDevice = device // 緩存目標設備
    val cxrApi = CxrApi.getInstance()
    // 調用CxrApi連接接口:需傳入緩存的UUID、MAC地址與回調
    val connectResult = cxrApi.connectBluetooth(
        context = context,
        socketUuid = savedUuid ?: "", // 從onConnectionInfo緩存獲取
        macAddress = savedMac ?: "", // 從onConnectionInfo緩存獲取
        callback = cxrApi.getBluetoothStatusCallback() as BluetoothStatusCallback // 複用已配置的回調
    )
    // 檢查連接請求是否發起成功(非實際連接結果,僅請求狀態)
    if (connectResult != ValueUtil.CxrStatus.REQUEST_SUCCEED) {
        Log.e(TAG, "發起藍牙連接請求失敗,結果:${connectResult?.name}")
    }
}

4Wi-Fi P2P 初始化(用於高速數據同步)

  • 藍牙連接成功後,自動初始化 Wi-Fi P2P 連接(用於傳輸大文件,如條碼圖像、快件信息)。
  • 監聽 Wi-Fi 連接狀態,連接成功後觸發未同步數據同步;失敗則打印錯誤原因。
  • 基於 SDK 接口initWifiP2P與isWifiP2PConnected實現狀態管理。

<!---->

/**
 * 5. 初始化Wi-Fi P2P(藍牙連接成功後觸發)
 * 作用:高速傳輸大文件(如條碼圖像、批量快件數據),彌補藍牙帶寬不足
 */
private fun initWifiP2P() {
    val cxrApi = CxrApi.getInstance()
    // 調用CxrApi初始化Wi-Fi P2P,傳入狀態回調
    val initResult = cxrApi.initWifiP2P(object : WifiP2PStatusCallback {
        /**
         * Wi-Fi P2P連接成功(觸發數據同步)
         */
        override fun onConnected() {
            Log.d(TAG, "Wi-Fi P2P連接成功,可開始同步數據")
            // 觸發未同步數據同步(如之前緩存的條碼圖像)
            DataSyncManager.syncUnsyncedData()
        }
 
        /**
         * Wi-Fi P2P連接斷開
         */
        override fun onDisconnected() {
            Log.d(TAG, "Wi-Fi P2P連接斷開,暫停數據同步")
        }
 
        /**
         * Wi-Fi P2P連接失敗(打印錯誤原因)
         * @param errorCode:錯誤碼(見ValueUtil.CxrWifiErrorCode)
         * - WIFI_DISABLED:手機Wi-Fi未開啓
         * - WIFI_CONNECT_FAILED:P2P連接失敗
         * - UNKNOWN:未知錯誤
         */
        override fun onFailed(errorCode: ValueUtil.CxrWifiErrorCode?) {
            Log.e(TAG, "Wi-Fi P2P連接失敗,錯誤碼:${errorCode?.name}")
        }
    })
    // 檢查Wi-Fi初始化請求是否發起成功
    if (initResult != ValueUtil.CxrStatus.REQUEST_SUCCEED) {
        Log.e(TAG, "Wi-Fi P2P初始化請求失敗,結果:${initResult?.name}")
    }
}

5訂閲眼鏡端消息(接收條碼識別結果)

  • 訂閲眼鏡端發送的 “條碼識別結果” 消息(使用可回覆訂閲模式MsgReplyCallback)。
  • 解析眼鏡端返回的識別結果(成功 / 失敗、條碼文本、圖像數據),觸發後續業務邏輯(如快件信息校驗、分揀指引)。
  • 回覆眼鏡端 “結果已收到”,完成消息閉環。

<!---->

/**
 * 6. 訂閲眼鏡端消息:接收條碼識別結果(可回覆模式)
 * 消息名:glasses_result_scan(需與眼鏡端發送的消息名一致,見3.1.3)
 */
private fun subscribeGlassesResults() {
    val cxrApi = CxrApi.getInstance()
    // 調用CxrApi訂閲接口:傳入消息名與可回覆回調
    val subscribeResult = cxrApi.subscribe(
        name = "glasses_result_scan", // 消息名(與眼鏡端約定)
        cb = object : CxrApi.MsgReplyCallback {
            /**
             * 接收眼鏡端消息回調
             * @param name:消息名(驗證是否為目標消息)
             * @param args:結構化參數(Caps格式,存儲識別狀態、條碼文本)
             * @param value:二進制數據(可選,如條碼圖像)
             * @param reply:回覆對象(用於向眼鏡端發送“結果已收到”)
             */
            override fun onReceive(
                name: String,
                args: com.rokid.cxr.Caps,
                value: ByteArray?,
                reply: CxrApi.Reply?
            ) {
                Log.d(TAG, "收到眼鏡端條碼識別結果消息:$name")
                // 校驗參數合法性(args不能為空,否則無法解析結果)
                if (args.size() == 0) {
                    Log.e(TAG, "識別結果參數為空,無法解析")
                    return
                }
 
                // 解析識別結果(從Caps中按順序讀取參數)
                val resultStatus = args.at(0).getString() // 第1個參數:狀態(scan_success/scan_failed)
                val resultText = args.at(1).getString()   // 第2個參數:條碼文本(成功時為單號,失敗時為原因)
                val imageData = if (args.size() > 2) args.at(2).getBinary().data else null // 第3個參數:條碼圖像(可選)
 
                // 分支1:本地識別成功→直接處理快件信息
                if (resultStatus == "scan_success") {
                    ExpressManager.processExpressInfo(resultText, imageData)
                } 
                // 分支2:本地識別失敗→觸發雲端識別
                else {
                    CloudBarcodeDecoder.decode(imageData) { cloudResult ->
                        if (cloudResult != null) {
                            ExpressManager.processExpressInfo(cloudResult, imageData)
                        } else {
                            Log.e(TAG, "本地+雲端解析均失敗,需人工處理")
                        }
                    }
                }
 
                // 回覆眼鏡端:告知“結果已收到”(完成消息閉環)
                val replyCaps = com.rokid.cxr.Caps()
                replyCaps.write("result_received") // 回覆內容(簡單狀態標識)
                reply?.end(replyCaps) // 發送回覆
            }
        }
    )
 
    // 檢查訂閲請求是否成功
    if (subscribeResult != 0) {
        Log.e(TAG, "訂閲條碼識別結果消息失敗,錯誤碼:$subscribeResult")
        // 錯誤碼説明:-1=參數錯誤(如消息名為空),-2=重複訂閲
    }
}

(三)關鍵功能技術説明

1.設備連接與雙模切換

藍牙保活與重連
  • 保活機制:通過CXR-M SDK的isBluetoothConnected定期檢查連接狀態,閒置時維持低功耗連接,避免頻繁斷連。
  • 重連邏輯:斷開後 3 秒內自動調用connectBluetooth複用savedUuid與savedMac重連,3 次失敗後觸發語音提醒工作人員。
 Wi-Fi P2P 自動觸發
  • 觸發條件:當檢測到需同步文件(如條碼圖像、快件信息)時,自動調用initWifiP2P初始化 Wi-Fi 連接,同步完成後 30 秒自動釋放資源。
  • 狀態監聽:通過isWifiP2PConnected判斷 Wi-Fi 狀態,未連接時緩存數據,連接後自動觸發同步。

2. 快件信息採集與識別

條碼掃描實現

利用眼鏡端相機接口實現條碼快速識別,配合 AI 優化識別算法:

  1. 相機配置:通過 CXR-M SDK setPhotoParams設置掃描分辨率(推薦 1920x1080),調用openGlassCamera打開眼鏡端相機,takeGlassPhoto拍攝條碼圖像。
  2. 本地識別:眼鏡端通過 CXR-S SDK 的圖像識別能力解析條碼信息,若本地識別失敗,將圖像通過 Wi-Fi 同步至手機端進行雲端識別。
  3. 信息校驗:手機端接收條碼信息後,調用雲端 API 校驗快件單號合法性、收件人信息完整性,通過提詞器場景(setWordTipsText)在眼鏡端顯示校驗結果。
語音指令交互

基於 Rokid 語音識別能力,支持以下核心指令:

  • 主動觸發:“掃描快件”“確認歸類”“查詢庫存” 等操作指令。
  • 被動反饋:眼鏡端通過 TTS 接口(sendTTSContent)播報 “掃描成功”“請歸類至 A 區 3 號架” 等反饋信息。

3. 智能歸類與指引

歸類規則引擎
  1. 雲端配置:快遞站根據區域、收件人地址、快件類型預設歸類規則(如 “同城件→A 區”“大件→B 區”)。
  2. 實時匹配:手機端接收快件信息後,調用雲端 API 獲取歸類結果,通過sendStream接口將指引信息推送至眼鏡端。
  3. 視覺指引:在眼鏡端自定義界面(openCustomView)顯示歸類區域示意圖,配合語音播報完成精準分揀。
異常處理機制
  • 條碼識別失敗:語音提示 “請調整角度重新掃描”,並在提詞器顯示操作指引。
  • 歸類規則不存在:自動標記為 “待人工處理”,同步至管理系統並提醒工作人員。
  • 網絡中斷:數據緩存至手機端(sendStream臨時存儲),網絡恢復後自動同步(startSync)。

4. 數據實時同步與管理

  1. 本地緩存:手機端通過 CXR-M SDK 的sendStream接口緩存快件信息與圖像,保障離線狀態下的操作連續性。
  2. 雲端同步:Wi-Fi 連接狀態下,調用startSync接口將緩存數據同步至雲端,支持單個文件同步(syncSingleFile)與批量同步。
  3. 狀態監聽:通過MediaFilesUpdateListener監聽眼鏡端媒體文件更新,確保掃描圖像無遺漏同步。

四、核心難點與解決方案

難點 1:移動場景下設備連接穩定性

問題:快遞站空間大、人員移動頻繁,藍牙連接易受干擾,Wi-Fi 切換需無縫銜接。解決方案:

  • 實現藍牙與 Wi-Fi 雙模自動切換:藍牙負責日常指令傳輸,Wi-Fi 觸發同步時自動連接,通過isBluetoothConnected與isWifiP2PConnected監聽狀態。
  • 優化藍牙掃描策略:基於 CXR-M SDK 的BluetoothHelper過濾 Rokid 設備 UUID,減少無效掃描消耗,提升連接速度。

難點 2:條碼識別準確率與速度平衡

問題:快件條碼可能存在污損、褶皺,需兼顧識別速度與準確率。解決方案:

  • 相機參數優化:通過setPhotoParams調整分辨率與壓縮質量,在不影響識別的前提下降低圖像傳輸延遲。
  • 本地 + 雲端雙識別機制:眼鏡端本地優先識別(CXR-S SDK 圖像處理能力),失敗後 300ms 內自動觸發雲端識別,保障流程不中斷。

難點 3:多指令併發處理

問題:工作人員可能連續觸發 “掃描”“歸類”“查詢” 等指令,需避免指令衝突。解決方案:

  • 指令隊列管理:手機端維護指令優先級隊列,語音指令與視覺操作指令分類處理,高優先級指令(如掃描確認)優先執行。
  • 狀態反饋機制:通過提詞器實時顯示當前操作狀態(如 “掃描中”“同步中”),避免重複觸發。

五、結語:讓技術提升工作體驗

通過項目實踐,我們在設備協同、場景配置與異常處理等方面積累了重要經驗。在設備協同方面,總結出“藍牙保活 + Wi-Fi 同步”的雙模通信方案,並藉助CXR-M SDK的deinitBluetooth與deinitWifiP2P接口優化資源釋放邏輯,有效降低了設備功耗。在場景適配方面,提煉出快遞場景專屬的提詞器配置模板與相機參數組合,為同類物流場景提供了可直接複用的配置基礎。在系統穩定性方面,形成了涵蓋設備斷連、識別失敗、網絡中斷等8類常見異常的標準化處理流程,並基於SDK回調接口構建了快速恢復機制,提升了系統的魯棒性。

着眼於未來應用,我們持續推進技術融合與功能優化。在AI能力方面,引入Rokid AI大模型,實現了快件破損識別與收件人信息脱敏處理,進一步提升了業務的智能化水平。在多語言支持方面,利用翻譯場景接口(sendTranslationContent)適配國際快件場景,支持多語言語音指令與信息顯示,拓展了系統的適用範圍。在設備管理方面,基於CXR-M SDK的設備狀態監聽接口(如BatteryLevelUpdateListener),實現了眼鏡端電量、亮度等關鍵狀態的遠程管理,為設備的持續穩定運行提供了有力保障。

綜上,本次技術提升工作不僅沉澱了多項可複用的實踐經驗,也通過持續迭代拓展了系統的智能化邊界與應用場景。未來,我們將繼續深化AI與業務場景的融合,優化設備協同與資源管理機制,為物流行業數智化升級提供更可靠、高效的技術支撐。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.