

编辑本文围绕烹饪场景的全流程需求,详细阐述如何利用 Rokid CXR-M SDK 的核心与扩展能力,开发一款功能完整、体验流畅的智能厨艺助手应用。通过手机与 Rokid Glasses 的深度协同,结合 JSON 配置化设计与多模块联动,实现 AR 菜谱可视化、食材量化指引、多模式交互、烹饪数据记录、云端菜谱同步等全场景功能,既保证功能完整性,又避免冗余 SDK 调用,为开发者提供一套 “实用全面、易落地、可扩展” 的 AI+AR 烹饪解决方案,让不同水平的用户都能轻松做出美味菜品。
居家烹饪不仅需要实时指引,还涉及食材管理、菜谱扩展、数据追溯等全流程需求:新手面临 “步骤看不懂、用量拿不准、操作断档” 的问题;进阶用户需要 “自定义菜谱、云端同步、烹饪记录” 功能;家庭用户关注 “食材过期提醒、多人共享菜谱” 等实用功能。传统工具仅解决单一环节痛点,而 Rokid CXR-M SDK 的多能力协同,能实现 “从食材准备到烹饪完成” 的全流程赋能。
Rokid Glasses 的 AR 显示 + 语音交互 + 图像采集能力,配合 CXR-M SDK 的核心(蓝牙连接、AR 渲染、TTS)与扩展(Wi-Fi P2P、相机控制、场景回调)API,构建 “虚实融合 + 全流程覆盖” 的闭环系统:AR 界面提供精准指引,蓝牙 + Wi-Fi 双模通信保障数据传输,相机控制支持食材识别,TTS + 按键 + 语音指令实现多模式交互,既满足核心烹饪指引需求,又覆盖食材管理、数据记录等延伸场景。
围绕烹饪全流程,设计五大实用模块,兼顾基础需求与进阶功能:
采用 “端 - 边 - 云” 轻量协同架构,分层明确且无冗余,兼顾功能完整性与性能:
结合蓝牙与 Wi-Fi P2P 双模通信,满足不同数据传输需求,确保功能完整性:
class CookingDeviceManager {companion object {private lateinit var context: Context
private var isBluetoothConnected = falseprivate var isWifiP2PConnected = falsefun init(context: Context) {this.context = context
CxrApi.getInstance().init(context) // SDK核心初始化}// 蓝牙连接(控制指令、文本传输)fun connectBluetooth(bluetoothDevice: BluetoothDevice) {
CxrApi.getInstance().initBluetooth(context, bluetoothDevice, object : BluetoothStatusCallback {override fun onConnected() {
isBluetoothConnected = truesendVoiceTip("蓝牙连接成功,正在初始化Wi-Fi传输")initWifiP2P() // 自动初始化Wi-Fi P2P}override fun onDisconnected() {
isBluetoothConnected = falsesendVoiceTip("蓝牙连接断开,请重新配对")}override fun onFailed(errorCode: ValueUtil.CxrBluetoothErrorCode?) {sendToast("蓝牙连接失败,错误码:${errorCode?.name}")}override fun onConnectionInfo(socketUuid: String?, macAddress: String?, rokidAccount: String?, glassesType: Int) {
Log.d("DeviceManager", "设备信息:MAC=$macAddress, 型号=$glassesType")}})}// Wi-Fi P2P初始化(高清资源、图像传输)private fun initWifiP2P() {
CxrApi.getInstance().initWifiP2P(object : WifiP2PStatusCallback {override fun onConnected() {
isWifiP2PConnected = truesendVoiceTip("Wi-Fi连接成功,可加载高清菜谱资源")}override fun onDisconnected() {
isWifiP2PConnected = falsesendVoiceTip("Wi-Fi连接断开,切换至基础模式")}override fun onFailed(errorCode: ValueUtil.CxrWifiErrorCode?) {sendToast("Wi-Fi连接失败,仅支持基础功能")}})}// 语音播报(SDK核心TTS API)fun sendVoiceTip(content: String) {if (isBluetoothConnected) {
CxrApi.getInstance().sendTtsContent(content)}}// 发送JSON数据(如菜谱配置)fun sendJsonData(jsonStr: String, callback: SendStatusCallback? = null) {if (isBluetoothConnected) {
CxrApi.getInstance().sendStream(
ValueUtil.CxrStreamType.DATA,
jsonStr.toByteArray(),"recipe_data.json",
callback
)}}// 检查设备连接状态fun isDeviceReady() = isBluetoothConnected
fun isHighSpeedMode() = isWifiP2PConnected
}}
通过 JSON 实现 AR 界面结构、菜谱数据、资源配置的统一管理,支持文本、图标、进度条等多元素展示,兼顾美观与实用:
class ARRecipeGuide {companion object {private var currentStep = 0private lateinit var recipeData: RecipeJson
private val AR_BASE_CONFIG = """
{
"type": "LinearLayout",
"props": {
"layout_width": "match_parent",
"layout_height": "match_parent",
"orientation": "vertical",
"gravity": "top|center",
"backgroundColor": "#80000000",
"padding": "30dp"
},
"children": [
{
"type": "TextView",
"props": {
"id": "tv_title",
"textSize": "24sp",
"textColor": "#FFFFFF",
"marginBottom": "15dp",
"textStyle": "bold"
}
},
{
"type": "ProgressBar",
"props": {
"id": "pb_step",
"layout_width": "250dp",
"layout_height": "8dp",
"progress": 0,
"max": 100,
"progressColor": "#FFCC00",
"bgColor": "#AAAAAA",
"marginBottom": "15dp"
}
},
{
"type": "TextView",
"props": {
"id": "tv_progress",
"textSize": "16sp",
"textColor": "#FFCC00",
"marginBottom": "20dp"
}
},
{
"type": "ImageView",
"props": {
"id": "iv_step_icon",
"layout_width": "120dp",
"layout_height": "120dp",
"marginBottom": "15dp"
}
},
{
"type": "TextView",
"props": {
"id": "tv_step_desc",
"textSize": "18sp",
"textColor": "#FFFFFF",
"maxWidth": "320dp",
"gravity": "center",
"marginBottom": "10dp"
}
},
{
"type": "TextView",
"props": {
"id": "tv_step_ingredient",
"textSize": "16sp",
"textColor": "#AAAAAA",
"marginBottom": "10dp"
}
},
{
"type": "TextView",
"props": {
"id": "tv_step_time",
"textSize": "16sp",
"textColor": "#4CAF50"
}
}
]
}
""".trimIndent()// 初始化AR菜谱(加载JSON配置+资源)fun initARGuide(recipeJson: RecipeJson) {this.recipeData = recipeJson
currentStep = 0val totalSteps = recipeData.steps.size
val progress = ((currentStep + 1).toFloat() / totalSteps * 100).toInt()// 合并基础配置与菜谱数据val arViewJson = mergeARConfig(AR_BASE_CONFIG, recipeJson, currentStep, progress)val status = CxrApi.getInstance().openCustomView(arViewJson)if (status == ValueUtil.CxrStatus.REQUEST_SUCCEED) {val stepData = recipeJson.steps[currentStep]// 上传步骤图标(Wi-Fi连接时加载高清图,否则用低清图)if (CookingDeviceManager.isHighSpeedMode()) {uploadStepIcon(stepData.hdIconName)} else {uploadStepIcon(stepData.lqIconName)}
CookingDeviceManager.sendVoiceTip("第一步,${stepData.desc},预计${stepData.time}分钟")} else {
CookingDeviceManager.sendVoiceTip("菜谱加载失败,请重试")}}// 合并AR配置与菜谱数据private fun mergeARConfig(baseConfig: String, recipe: RecipeJson, stepIndex: Int, progress: Int): String {val baseJson = JsonParser.parseString(baseConfig).asJsonObject
val stepData = recipe.steps[stepIndex]val totalSteps = recipe.steps.size
baseJson.getAsJsonArray("children").forEach { child ->val childObj = child.asJsonObject
val childProps = childObj.getAsJsonObject("props")when (childProps.get("id")?.asString) {"tv_title" -> childProps.addProperty("text", recipe.name)"pb_step" -> childProps.addProperty("progress", progress)"tv_progress" -> childProps.addProperty("text", "步骤 ${stepIndex+1}/$totalSteps")"tv_step_desc" -> childProps.addProperty("text", stepData.desc)"tv_step_ingredient" -> childProps.addProperty("text", "食材:${stepData.ingredient}")"tv_step_time" -> childProps.addProperty("text", "预计时长:${stepData.time}分钟")"iv_step_icon" -> childProps.addProperty("name", if (CookingDeviceManager.isHighSpeedMode()) stepData.hdIconName else stepData.lqIconName)}}return Gson().toJson(baseJson)}// 上传步骤图标(SDK扩展API)private fun uploadStepIcon(iconName: String) {val iconList = mutableListOf<IconInfo>()val bitmap = BitmapFactory.decodeResource(context.resources, context.resources.getIdentifier(iconName, "drawable", context.packageName))val base64Str = bitmapToBase64(bitmap)
iconList.add(IconInfo(iconName, base64Str))
CxrApi.getInstance().sendCustomViewIcons(iconList)}// Bitmap转Base64private fun bitmapToBase64(bitmap: Bitmap): String {val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 80, bos)val bytes = bos.toByteArray()return Base64.encodeToString(bytes, Base64.DEFAULT)}// 切换下一步fun nextStep() {if (currentStep >= recipeData.steps.size - 1) {finishCooking()return}
currentStep++val totalSteps = recipeData.steps.size
val progress = ((currentStep + 1).toFloat() / totalSteps * 100).toInt()val stepData = recipeData.steps[currentStep]// 构建更新JSONval updateJson = """
[
{
"action": "update",
"id": "pb_step",
"props": {"progress": $progress}
},
{
"action": "update",
"id": "tv_progress",
"props": {"text": "步骤 ${currentStep+1}/$totalSteps"}
},
{
"action": "update",
"id": "tv_step_desc",
"props": {"text": "${stepData.desc}"}
},
{
"action": "update",
"id": "tv_step_ingredient",
"props": {"text": "食材:${stepData.ingredient}"}
},
{
"action": "update",
"id": "tv_step_time",
"props": {"text": "预计时长:${stepData.time}分钟"}
},
{
"action": "update",
"id": "iv_step_icon",
"props": {"name": "${if (CookingDeviceManager.isHighSpeedMode()) stepData.hdIconName else stepData.lqIconName}"}
}
]
""".trimIndent()
CxrApi.getInstance().updateCustomView(updateJson)// 上传新步骤图标if (CookingDeviceManager.isHighSpeedMode()) {uploadStepIcon(stepData.hdIconName)} else {uploadStepIcon(stepData.lqIconName)}
CookingDeviceManager.sendVoiceTip("下一步,${stepData.desc},预计${stepData.time}分钟")}// 烹饪完成private fun finishCooking() {val updateJson = """
[
{
"action": "update",
"id": "pb_step",
"props": {"progress": 100}
},
{
"action": "update",
"id": "tv_progress",
"props": {"text": "烹饪完成!"}
},
{
"action": "update",
"id": "tv_step_desc",
"props": {"text": "恭喜完成${recipeData.name},快去品尝吧~"}
},
{
"action": "update",
"id": "tv_step_ingredient",
"props": {"text": "感谢使用AR智慧烹饪助手"}
},
{
"action": "update",
"id": "tv_step_time",
"props": {"text": ""}
},
{
"action": "update",
"id": "iv_step_icon",
"props": {"name": "cooking_finish"}
}
]
""".trimIndent()
CxrApi.getInstance().updateCustomView(updateJson)uploadStepIcon("cooking_finish")
CookingDeviceManager.sendVoiceTip("烹饪完成,恭喜你")// 记录烹饪数据
CookingRecordManager.saveCookingRecord(recipeData.id, recipeData.name)}// 关闭AR界面fun closeARGuide() {
CxrApi.getInstance().closeCustomView()}}}// 全功能菜谱JSON数据类data class RecipeJson(val id: Int,val name: String,val difficulty: String, // 难度:简单/中等/困难val totalTime: Int, // 总时长(分钟)val steps: List<RecipeStepJson>)data class RecipeStepJson(val desc: String, // 步骤描述val ingredient: String, // 食材用量val time: Int, // 预计时长(分钟)val hdIconName: String, // 高清图标名称val lqIconName: String // 低清图标名称(蓝牙模式下使用))
支持眼镜按键、语音指令、手机触控三种交互方式,适配烹饪中 “双手油污”“远距离操作” 等不同场景:
class CookingInteractionManager {companion object {// 初始化眼镜按键监听(SDK核心交互API)fun initKeyListener() {
CxrApi.getInstance().setAiEventListener(object : AiEventListener {override fun onAiKeyDown() {// 短按:重复当前步骤;长按:下一步Handler(Looper.getMainLooper()).postDelayed({
ARRecipeGuide.nextStep()}, 500) // 长按500ms触发下一步}override fun onAiKeyUp() {// 短按松开:重复当前步骤val currentStepData = ARRecipeGuide.recipeData.steps[ARRecipeGuide.currentStep]
CookingDeviceManager.sendVoiceTip("当前步骤:${currentStepData.desc},所需食材:${currentStepData.ingredient}")}override fun onAiExit() {// 退出按键:关闭AR界面
ARRecipeGuide.closeARGuide()
CookingDeviceManager.sendVoiceTip("烹饪已取消")}})}// 语音指令识别(支持多指令)fun handleVoiceCommand(command: String) {when {
command.contains("下一步") -> ARRecipeGuide.nextStep()
command.contains("重复") -> {val currentStepData = ARRecipeGuide.recipeData.steps[ARRecipeGuide.currentStep]
CookingDeviceManager.sendVoiceTip("当前步骤:${currentStepData.desc},所需食材:${currentStepData.ingredient}")}
command.contains("暂停") -> {
CookingDeviceManager.sendVoiceTip("已暂停,说继续或按眼镜按键恢复")}
command.contains("继续") -> {
CookingDeviceManager.sendVoiceTip("已继续烹饪")}
command.contains("退出") -> ARRecipeGuide.closeARGuide()
command.contains("难度") -> {
CookingDeviceManager.sendVoiceTip("当前菜品难度:${ARRecipeGuide.recipeData.difficulty}")}
command.contains("总时长") -> {
CookingDeviceManager.sendVoiceTip("当前菜品总时长:${ARRecipeGuide.recipeData.totalTime}分钟")}}}// 手机触控交互(配合APP界面)fun onPhoneTouch(action: String) {when (action) {"next_step" -> ARRecipeGuide.nextStep()"repeat_step" -> handleVoiceCommand("重复")"exit" -> ARRecipeGuide.closeARGuide()}}}}
采用轻量化 SQLite 数据库存储食材与烹饪记录,支持 JSON 格式导入 / 导出,兼顾本地使用与云端同步:
class IngredientManager {companion object {private lateinit var db: SQLiteDatabase
private const val DB_NAME = "cooking_db"private const val TABLE_INGREDIENT = "ingredient"// 初始化数据库fun init(context: Context) {
db = context.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null)// 创建食材表
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_INGREDIENT (" +"id INTEGER PRIMARY KEY AUTOINCREMENT," +"name TEXT NOT NULL," +"quantity TEXT NOT NULL," +"add_time INTEGER NOT NULL," +"expire_time INTEGER NOT NULL)")}// 添加食材(支持JSON导入)fun addIngredient(ingredientJson: String): Boolean {return try {val ingredient = Gson().fromJson(ingredientJson, Ingredient::class.java)val values = ContentValues().apply {put("name", ingredient.name)put("quantity", ingredient.quantity)put("add_time", ingredient.addTime)put("expire_time", ingredient.expireTime)}
db.insert(TABLE_INGREDIENT, null, values) != -1L} catch (e: Exception) {false}}// 检查过期食材(每天启动时调用)fun checkExpiredIngredients(): List<String> {val expiredList = mutableListOf<String>()val cursor = db.query(TABLE_INGREDIENT, null, "expire_time < ?", arrayOf(System.currentTimeMillis().toString()), null, null, null)while (cursor.moveToNext()) {val name = cursor.getString(cursor.getColumnIndexOrThrow("name"))
expiredList.add(name)}
cursor.close()// 语音提示过期食材if (expiredList.isNotEmpty()) {
CookingDeviceManager.sendVoiceTip("以下食材已过期:${expiredList.joinToString("、")},请及时处理")}return expiredList
}// 根据食材推荐菜谱(匹配度≥60%)fun recommendRecipes(recipes: List<RecipeJson>): List<RecipeJson> {val stockIngredients = mutableListOf<String>()val cursor = db.query(TABLE_INGREDIENT, arrayOf("name"), null, null, null, null, null)while (cursor.moveToNext()) {
stockIngredients.add(cursor.getString(0))}
cursor.close()return recipes.filter { recipe ->val recipeIngredients = recipe.steps.flatMap { step ->
step.ingredient.split("、").map { it.split(":")[0].trim() }}.distinct()val matchCount = recipeIngredients.count { stockIngredients.contains(it) }
matchCount.toFloat() / recipeIngredients.size >= 0.6}}// 导出食材为JSONfun exportIngredientsToJson(): String {val ingredients = mutableListOf<Ingredient>()val cursor = db.query(TABLE_INGREDIENT, null, null, null, null, null, null)while (cursor.moveToNext()) {
ingredients.add(Ingredient(
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
name = cursor.getString(cursor.getColumnIndexOrThrow("name")),
quantity = cursor.getString(cursor.getColumnIndexOrThrow("quantity")),
addTime = cursor.getLong(cursor.getColumnIndexOrThrow("add_time")),
expireTime = cursor.getLong(cursor.getColumnIndexOrThrow("expire_time"))))}
cursor.close()return Gson().toJson(ingredients)}}}// 食材数据类data class Ingredient(val id: Int = 0,val name: String,val quantity: String,val addTime: Long = System.currentTimeMillis(),val expireTime: Long
)// 烹饪记录管理class CookingRecordManager {companion object {private lateinit var db: SQLiteDatabase
private const val TABLE_RECORD = "cooking_record"fun init(context: Context) {
db = context.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null)// 创建烹饪记录表
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_RECORD (" +"id INTEGER PRIMARY KEY AUTOINCREMENT," +"recipe_id INTEGER NOT NULL," +"recipe_name TEXT NOT NULL," +"cooking_time INTEGER NOT NULL," +"finish_time INTEGER NOT NULL," +"rating INTEGER DEFAULT 0)") // 评分:0-5分}// 保存烹饪记录fun saveCookingRecord(recipeId: Int, recipeName: String) {val values = ContentValues().apply {put("recipe_id", recipeId)put("recipe_name", recipeName)put("cooking_time", System.currentTimeMillis() - startTime) // 假设startTime在启动烹饪时记录put("finish_time", System.currentTimeMillis())}
db.insert(TABLE_RECORD, null, values)}// 导出记录为JSONfun exportRecordsToJson(): String {val records = mutableListOf<CookingRecord>()val cursor = db.query(TABLE_RECORD, null, null, null, null, null, "finish_time DESC")while (cursor.moveToNext()) {
records.add(CookingRecord(
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
recipeId = cursor.getInt(cursor.getColumnIndexOrThrow("recipe_id")),
recipeName = cursor.getString(cursor.getColumnIndexOrThrow("recipe_name")),
cookingTime = cursor.getLong(cursor.getColumnIndexOrThrow("cooking_time")),
finishTime = cursor.getLong(cursor.getColumnIndexOrThrow("finish_time")),
rating = cursor.getInt(cursor.getColumnIndexOrThrow("rating"))))}
cursor.close()return Gson().toJson(records)}}}// 烹饪记录数据类data class CookingRecord(val id: Int,val recipeId: Int,val recipeName: String,val cookingTime: Long, // 实际烹饪时长(毫秒)val finishTime: Long,val rating: Int // 评分)
支持本地 JSON 菜谱与云端同步,通过 HTTP 接口实现菜谱上传、下载、分享,扩展菜品来源:
class CloudRecipeManager {companion object {private const val CLOUD_API_URL = "https://api.cooking-assistant.com/recipes"// 从云端下载菜谱(JSON格式)suspend fun downloadCloudRecipes(): List<RecipeJson>? {return try {val response = RetrofitClient.instance.getCloudRecipes()if (response.isSuccessful) {
response.body()?.let {Gson().fromJson(it, object : TypeToken<List<RecipeJson>>() {}.type)}} else {null}} catch (e: Exception) {null}}// 上传本地菜谱到云端(JSON格式)suspend fun uploadLocalRecipe(recipeJson: RecipeJson): Boolean {return try {val jsonStr = Gson().toJson(recipeJson)val response = RetrofitClient.instance.uploadRecipe(jsonStr)
response.isSuccessful
} catch (e: Exception) {false}}// 分享菜谱(生成JSON分享链接)fun shareRecipe(recipeJson: RecipeJson): String {val jsonStr = Gson().toJson(recipeJson)val base64Str = Base64.encodeToString(jsonStr.toByteArray(), Base64.URL_SAFE)return "$CLOUD_API_URL/share?data=$base64Str"}}}// Retrofit客户端(简化版)object RetrofitClient {private val retrofit = Retrofit.Builder().baseUrl(CLOUD_API_URL).addConverterFactory(ScalarsConverterFactory.create()).build()val instance = retrofit.create(RecipeApi::class.java)}// API接口interface RecipeApi {@GET("/list")suspend fun getCloudRecipes(): Response<String>@POST("/upload")@Bodysuspend fun uploadRecipe(@Body jsonStr: String): Response<Unit>}
本项目基于 Rokid CXR-M SDK 的核心与扩展能力,结合 JSON 配置化设计,打造了一款功能完整、体验流畅的 AR 智慧烹饪助手。核心优势在于 “全流程覆盖 + 灵活配置 + 多模式交互”:通过 JSON 统一管理界面与数据,实现 “零代码扩展菜谱”;支持蓝牙 + Wi-Fi 双模通信,兼顾稳定性与功能丰富度;多模式交互适配烹饪场景的实际需求,真正实现 “解放双手”。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。