一、創意緣起:工作堆積如山,科技順其有序
場景:
下午 2 點的快遞站倉庫,王師傅蹲在堆積如山的快件中,左手抱着一摞包裹,右手緊握掃碼槍對準條碼掃描。他需要頻繁彎腰將快件放入對應貨架格,汗水浸濕後背工裝。
當 Rokid AI Glasses 智能眼鏡遇見智慧物流
在快遞業務量持續增長的今天,快遞站工作人員面臨着巨大的分揀壓力。傳統的快件錄入需要反覆查看面單、手動輸入信息、分類擺放,整個過程耗時耗力且容易出錯。而 Rokid AI Glasses 的出現,為這一場景帶來了新的解決方案。
本文將詳細介紹如何利用 Rokid CXR-M(移動端)和 CXR-S(眼鏡端)SDK,構建一個解放雙手的快件錄入歸類助手,實現"所見即所得"的智能分揀體驗。
二、系統架構設計
架構總覽
系統採用 “眼鏡端採集 + 手機端協同 + 雲端同步” 的三層架構,核心依賴 Rokid SDK 實現設備交互與數據流轉:
• 終端層(CXR-S AI眼鏡)
作為“感知與輸出終端”,負責快件條碼識別、語音指令接收、操作指引顯示,基於 CXR-S SDK 實現本地 AI 識別與狀態監聽
• 業務邏輯層(CXR-M移動設備)
通過 CXR-M SDK 實現設備連接管理、數據緩存、雲端通信,承接眼鏡端採集的數據並同步至管理系統。
• 雲端層(數據服務)
提供快件信息校驗、歸類規則存儲、數據統計分析功能,通過 API 與手機端實時交互。
核心技術依賴
- 設備連接:基於 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 優化識別算法:
- 相機配置:通過 CXR-M SDK setPhotoParams設置掃描分辨率(推薦 1920x1080),調用openGlassCamera打開眼鏡端相機,takeGlassPhoto拍攝條碼圖像。
- 本地識別:眼鏡端通過 CXR-S SDK 的圖像識別能力解析條碼信息,若本地識別失敗,將圖像通過 Wi-Fi 同步至手機端進行雲端識別。
- 信息校驗:手機端接收條碼信息後,調用雲端 API 校驗快件單號合法性、收件人信息完整性,通過提詞器場景(setWordTipsText)在眼鏡端顯示校驗結果。
語音指令交互
基於 Rokid 語音識別能力,支持以下核心指令:
- 主動觸發:“掃描快件”“確認歸類”“查詢庫存” 等操作指令。
- 被動反饋:眼鏡端通過 TTS 接口(sendTTSContent)播報 “掃描成功”“請歸類至 A 區 3 號架” 等反饋信息。
3. 智能歸類與指引
歸類規則引擎
- 雲端配置:快遞站根據區域、收件人地址、快件類型預設歸類規則(如 “同城件→A 區”“大件→B 區”)。
- 實時匹配:手機端接收快件信息後,調用雲端 API 獲取歸類結果,通過sendStream接口將指引信息推送至眼鏡端。
- 視覺指引:在眼鏡端自定義界面(openCustomView)顯示歸類區域示意圖,配合語音播報完成精準分揀。
異常處理機制
- 條碼識別失敗:語音提示 “請調整角度重新掃描”,並在提詞器顯示操作指引。
- 歸類規則不存在:自動標記為 “待人工處理”,同步至管理系統並提醒工作人員。
- 網絡中斷:數據緩存至手機端(sendStream臨時存儲),網絡恢復後自動同步(startSync)。
4. 數據實時同步與管理
- 本地緩存:手機端通過 CXR-M SDK 的sendStream接口緩存快件信息與圖像,保障離線狀態下的操作連續性。
- 雲端同步:Wi-Fi 連接狀態下,調用startSync接口將緩存數據同步至雲端,支持單個文件同步(syncSingleFile)與批量同步。
- 狀態監聽:通過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與業務場景的融合,優化設備協同與資源管理機制,為物流行業數智化升級提供更可靠、高效的技術支撐。