From 08611349aa3c0002a0844ea7df63cbededb3c8d2 Mon Sep 17 00:00:00 2001 From: zxj <275873859@qq.com> Date: Wed, 6 Aug 2025 14:45:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B2=BE=E7=AE=80=E4=BA=86=E7=A7=B0sdk?= =?UTF-8?q?=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 271 ++++++++++++++++++ .../main/java/com/sw/dualscreen/GlobalData.kt | 4 + app/src/main/java/com/sw/dualscreen/MyApp.kt | 4 + .../sw/dualscreen/activity/MainActivity.kt | 42 ++- .../model/request/UserNutritionParam.kt | 2 +- .../sw/dualscreen/network/api/ApiService.kt | 2 +- .../SecondaryScreenPresentation.kt | 247 ++++++++++++---- .../dualscreen/repository/RemoteRepository.kt | 2 +- .../com/sw/dualscreen/sdk/SensorScaleUtils.kt | 30 +- .../sw/dualscreen/viewmodel/BaseViewModel.kt | 1 - .../sw/dualscreen/viewmodel/UserViewModel.kt | 6 +- .../layout/presentation_secondary_screen.xml | 25 +- app/src/main/res/values/strings.xml | 1 + .../arcface/camera/DualCameraHelper.java | 3 +- .../arcface/viewmodel/RecognizeViewModel.java | 6 +- 15 files changed, 545 insertions(+), 101 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..60b2650 --- /dev/null +++ b/README.md @@ -0,0 +1,271 @@ +{ +"info": { +"_postman_id": "adfceb8d-2c35-41bf-82d9-40b87e10524d", +"name": "档口机", +"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", +"_exporter_id": "751386" +}, +"item": [{ +"name": "device获取token", +"request": { +"auth": { +"type": "noauth" +}, +"method": "GET", +"header": [], +"url": { +" +raw": "http://device.shuziweidao.com:8889/sys/getEquipmentToken?qrcodeId=3ea47dc0-3cf0-3c2f-909c-265a9a65572e", +"protocol": "http", +"host": [ +"device", +"shuziweidao", +"com" +], +"port": "8889", +"path": [ +"sys", +"getEquipmentToken" +], +"query": [{ +"key": "qrcodeId", +"value": "3ea47dc0-3cf0-3c2f-909c-265a9a65572e" +} +] +} +}, +"response": [] +}, { +"name": "获取配置信息", +"request": { +"method": "GET", +"header": [{ +"key": "X-Access-Token", +"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTM4NDIzNDEsInVzZXJuYW1lIjoiYWRtaW4ifQ.gcKT7BeMWNve8NydRmTTrdXW8wdS7W_Ot9kVdtllyuc", +"type": "text" +} +], +"url": { +" +raw": "http://device.shuziweidao.com:8889/equipment/stEquipment/queryByEquipmentCode?equipmentCode=3ea47dc0-3cf0-3c2f-909c-265a9a65572e", +"protocol": "http", +"host": [ +"device", +"shuziweidao", +"com" +], +"port": "8889", +"path": [ +"equipment", +"stEquipment", +"queryByEquipmentCode" +], +"query": [{ +"key": "equipmentCode", +"value": "3ea47dc0-3cf0-3c2f-909c-265a9a65572e" +} +] +} +}, +"response": [] +}, { +"name": "获取业务服务器token", +"request": { +"method": "GET", +"header": [], +"url": { +" +raw": "http://192.168.1.207/gateway/local/sys/getEquipmentToken?qrcodeId=3ea47dc0-3cf0-3c2f-909c-265a9a65572e", +"protocol": "http", +"host": [ +"192", +"168", +"1", +"207" +], +"path": [ +"gateway", +"local", +"sys", +"getEquipmentToken" +], +"query": [{ +"key": "qrcodeId", +"value": "3ea47dc0-3cf0-3c2f-909c-265a9a65572e" +} +] +} +}, +"response": [] +}, { +"name": "获取人脸数据", +"request": { +"method": "GET", +"header": [{ +"key": "X-Access-Token", +"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiIyOCIsInR5cGUiOiI0IiwidXNlcklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMSJ9.Jy7Dc2k5FlqUAmGReRn-uEg3Du5gtlyUhXKUMZLlO-E", +"type": "text" +} +], +"url": { +"raw": "http://192.168.1.207/gateway/local/stapi/cquser/getUserFaceCache/v2?pageIndex=0", +"protocol": "http", +"host": [ +"192", +"168", +"1", +"207" +], +"path": [ +"gateway", +"local", +"stapi", +"cquser", +"getUserFaceCache", +"v2" +], +"query": [{ +"key": "pageIndex", +"value": "0" +} +] +} +}, +"response": [] +}, { +"name": "获取档口菜品信息", +"request": { +"method": "GET", +"header": [{ +"key": "X-Access-Token", +"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiIyOCIsInR5cGUiOiI0IiwidXNlcklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMSJ9.Jy7Dc2k5FlqUAmGReRn-uEg3Du5gtlyUhXKUMZLlO-E", +"type": "text" +} +], +"url": { +" +raw": "http://192.168.1.207/gateway/local/zhstapi/zhst/getRestInfoFoodsByType/stall?restId=1678234139391512577&type=0&foodName=西红柿", +"protocol": "http", +"host": [ +"192", +"168", +"1", +"207" +], +"path": [ +"gateway", +"local", +"zhstapi", +"zhst", +"getRestInfoFoodsByType", +"stall" +], +"query": [{ +"key": "restId", +"value": "1678234139391512577" +}, { +"key": "type", +"value": "0", +"description": "0全部 1餐次" +}, { +"key": "foodName", +"value": "西红柿" +} +] +} +}, +"response": [] +}, { +"name": "通过用户信息获取就餐数据", +"request": { +"method": "GET", +"header": [{ +"key": "X-Access-Token", +"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiIyOCIsInR5cGUiOiI0IiwidXNlcklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMSJ9.Jy7Dc2k5FlqUAmGReRn-uEg3Du5gtlyUhXKUMZLlO-E", +"type": "text" +} +], +"url": { +" +raw": "http://192.168.1.207/gateway/local/zhstapi/zhst/getUserNutritionData/face?restId=1678234139391512577&userId=4958698b725b4e89a514fa8552aa5fa3&foodId=1701417944860119041", +"protocol": "http", +"host": [ +"192", +"168", +"1", +"207" +], +"path": [ +"gateway", +"local", +"zhstapi", +"zhst", +"getUserNutritionData", +"face" +], +"query": [{ +"key": "restId", +"value": "1678234139391512577" +}, { +"key": "userId", +"value": "4958698b725b4e89a514fa8552aa5fa3" +}, { +"key": "foodId", +"value": "1701417944860119041" +} +] +} +}, +"response": [] +}, { +"name": "提交就餐数据", +"request": { +"method": "POST", +"header": [], +"body": { +"mode": "raw", +"raw": "", +"options": { +"raw": { +"language": "json" +} +} +}, +"url": { +" +raw": "http://192.168.1.207/gateway/local/zhstapi/zhst/userEatFood/doubleBalance/face/stall?restId=1678234139391512577&userId=4958698b725b4e89a514fa8552aa5fa3&foodId=1701417944860119041&faceTime&faceEndTime&eatWeight&foodWeight&deviceId", +"protocol": "http", +"host": [ +"192", +"168", +"1", +"207" +], +"path": [ +"gateway", +"local", +"zhstapi", +"zhst", +"userEatFood", +"doubleBalance", +"face", +"stall" +], +"body": [{ +"appVersion": "1", +"deviceId": "abe60247-99d1-3c2a-9f10-c0b76f4b3f3b", +"eatWeight": 0.278, +"faceEndTime": 1754385701611, +"faceTime": 1754385694475, +"foodId": "1701417944860119041", +"foodWeight": 0.278, +"restId": "1678234139391512577", +"userId": "1937692681894932481" +} +] +} +}, +"response": [] +} +] +} diff --git a/app/src/main/java/com/sw/dualscreen/GlobalData.kt b/app/src/main/java/com/sw/dualscreen/GlobalData.kt index 4f82636..5e96065 100644 --- a/app/src/main/java/com/sw/dualscreen/GlobalData.kt +++ b/app/src/main/java/com/sw/dualscreen/GlobalData.kt @@ -7,6 +7,10 @@ object GlobalData { // var globalEquipmentCode: String = "202501171634" var restId: String = "" + /** + * 设备id + */ + var deviceId: String = "" /** * app版本号 */ diff --git a/app/src/main/java/com/sw/dualscreen/MyApp.kt b/app/src/main/java/com/sw/dualscreen/MyApp.kt index a7dee2e..1ef5329 100644 --- a/app/src/main/java/com/sw/dualscreen/MyApp.kt +++ b/app/src/main/java/com/sw/dualscreen/MyApp.kt @@ -1,6 +1,7 @@ package com.sw.dualscreen import com.sw.plate.App +import com.sw.plate.utils.AppUtil import timber.log.Timber class MyApp : App() { @@ -11,5 +12,8 @@ class MyApp : App() { override fun onCreate() { super.onCreate() Timber.plant(Timber.DebugTree()) + val deviceId = AppUtil.getUDID(this) + Timber.d("UDID = ${AppUtil.getUDID(this)}") + GlobalData.deviceId = deviceId } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt index 549c269..5f974d6 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt @@ -6,6 +6,7 @@ import android.graphics.Typeface import android.hardware.display.DisplayManager import android.net.Uri import android.os.Bundle +import android.text.TextUtils import android.view.View import android.view.ViewOutlineProvider import android.widget.AdapterView @@ -36,7 +37,6 @@ import com.sw.dualscreen.sdk.SensorScaleUtils import com.sw.dualscreen.utils.GlideUtils import com.sw.dualscreen.view.CustomBottomSheetDialog import com.sw.dualscreen.viewmodel.UserViewModel -import com.sw.plate.utils.AppUtil import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import kotlinx.coroutines.launch @@ -56,6 +56,8 @@ class MainActivity : AppCompatActivity() { private var imageAnalysis: ImageAnalysis? = null private var cameraProviderFuture: ListenableFuture? = null private var canIdentify: Boolean = false + private var bottomSheetDialog: CustomBottomSheetDialog? = null + private var lastWeight: Double = 0.0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -63,7 +65,6 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.main) - Timber.d("UDID = ${AppUtil.getUDID(this)}") initView() setupSecondaryDisplay() setupCamera() @@ -84,10 +85,10 @@ class MainActivity : AppCompatActivity() { } binding.previewView.clipToOutline = true binding.tvToSearch.setOnClickListener { - val bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) { + bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) { updateCurrentFood(it) } - bottomSheetDialog.show(supportFragmentManager, "CustomBottomSheetDialog") + bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog") } val spinnerAdapter = ArrayAdapter( @@ -138,7 +139,15 @@ class MainActivity : AppCompatActivity() { binding.tvFoodName.text = foodInfo.foodName binding.previewView.visibility = View.GONE binding.ivImg.visibility = View.VISIBLE - GlideUtils.loadRoundCornerImage(this, foodInfo.imgUrl, binding.ivImg, 12) + val imgUrl = foodInfo.imgUrl + if (TextUtils.isEmpty(imgUrl)) { + takePhoto { imgUri -> + GlideUtils.loadRoundCornerImage(this, imgUri, binding.ivImg, 12) + presentation.updateFood(foodInfo.copy(imgUrl = imgUri.toString())) + } + } else { + GlideUtils.loadRoundCornerImage(this, foodInfo.imgUrl, binding.ivImg, 12) + } pauseAnalysis() } else { binding.tvFoodName.text = "-" @@ -158,7 +167,8 @@ class MainActivity : AppCompatActivity() { } } SensorScaleUtils.startScale { weight -> -// Timber.d("weight = $weight") + Timber.d("registerDataChange weight = $weight") + presentation.updateWeight(weight) if (weight > 0.0) { if (canIdentify) { canIdentify = false @@ -290,23 +300,30 @@ class MainActivity : AppCompatActivity() { // 拍照功能 private fun takePhoto(callback: (Uri) -> Unit) { + Timber.d("takePhoto") // 临时停止分析以避免干扰 isAnalyzing = false // 创建输出选项 - val outputFileOptions = ImageCapture.OutputFileOptions.Builder( - File( - cacheDir.path, "IMG_${System.currentTimeMillis()}.jpg" - ) - ).build() + val executor = ContextCompat.getMainExecutor(this) + val cacheDir = cacheDir + val photoFile = File.createTempFile( + "IMG_${System.currentTimeMillis()}", + ".jpg", + cacheDir + ) + + val cacheOutputOptions = + ImageCapture.OutputFileOptions.Builder(photoFile).build() // 执行拍照 imageCapture?.takePicture( - outputFileOptions, + cacheOutputOptions, executor, object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { runOnUiThread { val savedUri = outputFileResults.savedUri + Timber.d("takePhoto savedUri = $savedUri") ToastUtils.showToast("照片保存成功: $savedUri") if (savedUri != null) { callback(savedUri) @@ -347,6 +364,7 @@ class MainActivity : AppCompatActivity() { presentation.setStepChangeCallback { step -> if (step == 1) { updateCurrentFood(null) + bottomSheetDialog?.dismiss() } } presentation.show() diff --git a/app/src/main/java/com/sw/dualscreen/model/request/UserNutritionParam.kt b/app/src/main/java/com/sw/dualscreen/model/request/UserNutritionParam.kt index 1449384..bb97cda 100644 --- a/app/src/main/java/com/sw/dualscreen/model/request/UserNutritionParam.kt +++ b/app/src/main/java/com/sw/dualscreen/model/request/UserNutritionParam.kt @@ -14,5 +14,5 @@ data class UserNutritionParam( val faceEndTime: Long, val eatWeight: Double, val foodWeight: Double, - val deviceId: String = "" + val deviceId: String = GlobalData.deviceId ) : Parcelable diff --git a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt index 8837c56..457f9e4 100644 --- a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt +++ b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt @@ -97,6 +97,6 @@ interface ApiService { @POST suspend fun postUserNutritionData( @Url url: String = "${GlobalData.appBaseUrl}/zhstapi/zhst/userEatFood/doubleBalance/face/stall", - @Body param: UserNutritionParam + @Body param: List ): ApiResponse } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt index 4c61113..b802911 100644 --- a/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt +++ b/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt @@ -19,7 +19,6 @@ import android.view.View import android.view.ViewGroup import android.view.ViewOutlineProvider import android.view.ViewTreeObserver -import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.lifecycle.Observer @@ -32,10 +31,10 @@ import com.sw.dualscreen.databinding.PresentationSecondaryScreenBinding import com.sw.dualscreen.ext.dp import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.maskName +import com.sw.dualscreen.model.request.UserNutritionParam import com.sw.dualscreen.model.response.DinnerTypeInfo import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.UserNutritionData -import com.sw.dualscreen.sdk.SensorScaleUtils import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.GlideUtils import com.sw.dualscreen.viewmodel.UserViewModel @@ -49,9 +48,11 @@ import com.sw.plate.utils.arcface.PreviewConfig import com.sw.plate.utils.arcface.camera.CameraListener import com.sw.plate.utils.arcface.camera.DualCameraHelper import com.sw.plate.utils.arcface.face.constants.LivenessType +import com.sw.plate.utils.arcface.face.model.CompareResult import com.sw.plate.utils.arcface.face.model.FacePreviewInfo import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch import timber.log.Timber @@ -67,9 +68,6 @@ class SecondaryScreenPresentation( val recognizeViewModel: RecognizeViewModel ) : Presentation(context, display), ViewTreeObserver.OnGlobalLayoutListener { private lateinit var binding: PresentationSecondaryScreenBinding - private var cameraDevice: CameraDevice? = null - - // private var cameraCaptureSession: CameraCaptureSession? = null private lateinit var cameraManager: CameraManager private var cameraIdList: Array = arrayOf() @@ -77,13 +75,17 @@ class SecondaryScreenPresentation( private var currentFood: FoodInfo? = null private var lastFood: FoodInfo? = null private var userNutritionData: UserNutritionData? = null - private var dinnerType: DinnerTypeInfo? = null + private var dinnerTypeInfo: DinnerTypeInfo? = null private lateinit var itemUserNutritionBinding: ItemUserNutritionBinding private lateinit var stepChangeCallback: (Int) -> Unit + + // 上一次的计算热量结果 private var lastCalcResultInfo: UserNutritionUtils.CalcResultInfo? = null - private var startSensorScale: Boolean = false private var lastWeight = 0.0 - private val debouncer = Debouncer(1000) + private val debouncer = Debouncer(2000) + private var faceTime: Long = 0L // 人脸识别时的时间 + private var recognitionWeight: Double = 0.0 // 人脸识别时的重量 + private var lastFaceTrackId: Int = 0 // 上一次的人脸信息 // 当前步骤 var currentStep: Int = 0 @@ -104,17 +106,20 @@ class SecondaryScreenPresentation( activity.lifecycleScope.launch { viewModel.nutritionData.collect { Timber.d("registerDataChange nutritionData = $it") - if (it == null) return@collect userNutritionData = it + if (it == null) return@collect step3() itemUserNutritionBinding.tvUserName.text = it.userName.maskName() itemUserNutritionBinding.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}" + + updateWeight(lastWeight) } } activity.lifecycleScope.launch { - viewModel.dinnerTypeInfo.collect { - dinnerType = it + viewModel.dinnerTypeInfo.drop(1).collect { + Timber.d("registerDataChange dinnerTypeInfo = $it") + dinnerTypeInfo = it } } } @@ -155,8 +160,14 @@ class SecondaryScreenPresentation( fun step1() { Timber.d("step1") currentStep = 1 + //回复上一次的设置 + lastWeight = 0.0 + lastFood = null + lastCalcResultInfo = null + recognitionWeight = 0.0 + stepChangeCallback(currentStep) - binding.tvFoodInfo.text = "菜品识别中" + binding.tvFoodName.text = "菜品识别中" binding.ivFaceBg.visibility = View.GONE binding.flPreview.visibility = View.VISIBLE binding.ivImg.visibility = View.VISIBLE @@ -164,8 +175,6 @@ class SecondaryScreenPresentation( pauseCamera() binding.tvBottomTip.visibility = View.VISIBLE itemUserNutritionBinding.clNutritionData.visibility = View.GONE - stopSensorScale() - viewModel.getDinnerType() } /** @@ -176,14 +185,15 @@ class SecondaryScreenPresentation( currentStep = 2 stepChangeCallback(currentStep) currentFood = foodInfo + viewModel.getDinnerType() resumeCamera() - binding.tvFoodInfo.text = foodInfo.foodName + binding.tvFoodName.text = foodInfo.foodName binding.ivImg.visibility = View.GONE binding.flPreview.visibility = View.GONE binding.flFace.visibility = View.VISIBLE - binding.ivFaceBg.visibility = View.VISIBLE + binding.ivFaceBg.visibility = View.GONE binding.tvFaceTip.visibility = View.VISIBLE binding.tvBottomTip.visibility = View.VISIBLE itemUserNutritionBinding.clNutritionData.visibility = View.GONE @@ -196,7 +206,7 @@ class SecondaryScreenPresentation( if (currentStep == 1) { step2(foodInfo) } else { - binding.tvFoodInfo.text = foodInfo.foodName + binding.tvFoodName.text = foodInfo.foodName GlideUtils.loadRoundCornerImage( context, url = currentFood!!.imgUrl, @@ -218,10 +228,7 @@ class SecondaryScreenPresentation( ToastUtils.showToast("选中物品为空3") return } - binding.tvFoodInfo.text = currentFood!!.foodName - pauseCamera() - startSensorScale() - + binding.tvFoodName.text = currentFood!!.foodName GlideUtils.loadRoundCornerImage( context, url = currentFood!!.imgUrl, @@ -238,36 +245,40 @@ class SecondaryScreenPresentation( itemUserNutritionBinding.clNutritionData.visibility = View.VISIBLE } - private fun startSensorScale() { - Timber.d("startSensorScale startSensorScale = $startSensorScale") - if (startSensorScale) return - SensorScaleUtils.startScale { weight -> + fun updateWeight(weight: Double) { + Timber.d("updateWeight weight = $weight") + lastWeight = weight + Timber.d("updateWeight lastWeight = $weight") if (currentFood == null) { - Timber.e("startSensorScale currentFood is null") - return@startScale + Timber.e("updateWeight currentFood is null") + return } if (userNutritionData == null) { - Timber.e("startSensorScale userNutritionData is null") - return@startScale + Timber.e("updateWeight userNutritionData is null") + return } - if (dinnerType == null) { - Timber.e("startSensorScale dinnerType is null") - return@startScale + if (dinnerTypeInfo == null) { + Timber.e("updateWeight dinnerType is null") + return } // 重量和 当前食物都没变,则不再计算 - if (weight == lastWeight && lastFood == currentFood) return@startScale - Timber.d("registerDataChange weight = $weight") + if (lastFood == currentFood) { +// Timber.e("updateWeight 物品未改变") + return + } lastFood = currentFood - lastWeight = weight debouncer.debounce { + val dinnerType = dinnerTypeInfo!!.dinnerType!!.dinnerType!! + Timber.d("updateWeight foodName = ${currentFood!!.foodName}, dinnerType = $dinnerType, userId = ${userNutritionData!!.userId}") val calcResultInfo = UserNutritionUtils.calculateNutrition( currentFood!!, userNutritionData!!, weight, - dinnerType = dinnerType!!.dinnerType!!.dinnerType!! + dinnerType = dinnerType ) - Timber.d("calcResultInfo = $calcResultInfo") + Timber.d("updateWeight calcResultInfo = $calcResultInfo") if (lastCalcResultInfo == calcResultInfo) { + Timber.d("updateWeight 与上次计算结果无变化") return@debounce } lastCalcResultInfo = calcResultInfo @@ -318,11 +329,6 @@ class SecondaryScreenPresentation( ) } } - } - } - - private fun stopSensorScale() { - SensorScaleUtils.closeScale() } private fun openCamera() { @@ -338,7 +344,11 @@ class SecondaryScreenPresentation( // 获取摄像头列表 cameraIdList = cameraManager.cameraIdList if (cameraIdList.isEmpty()) { - Toast.makeText(context, "没有可用的摄像头", Toast.LENGTH_SHORT).show() + ToastUtils.showToast("没有可用的摄像头") + return + } + if (cameraIdList.size < 3) { + ToastUtils.showToast("摄像头数量异常") return } @@ -347,27 +357,29 @@ class SecondaryScreenPresentation( } val cameraId = cameraIdList.last() Timber.d("openCamera cameraId = $cameraId") - cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() { + val c1 = cameraIdList[0] + val c2 = cameraIdList[1] + val c3 = cameraIdList[2] + cameraManager.openCamera(c3, object : CameraDevice.StateCallback() { override fun onOpened(device: CameraDevice) { - cameraDevice = device -// createCameraPreviewSession() recognizeViewModel.init( PreviewConfig( - 2, 4, 0, 0 + c3.toInt(), c2.toInt(), 90, 90 ) ) initRgbCamera() - resumeCamera() +// resumeCamera() + if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) { + initIrCamera() + } } override fun onDisconnected(device: CameraDevice) { device.close() - cameraDevice = null } override fun onError(device: CameraDevice, error: Int) { device.close() - cameraDevice = null } }, null) } catch (e: Exception) { @@ -383,9 +395,12 @@ class SecondaryScreenPresentation( private var isRecognition = false private var rgbCameraHelper: DualCameraHelper? = null private var rgbFaceRectTransformer: FaceRectTransformer? = null - private val livenessType = LivenessType.RGB + private val livenessType = LivenessType.IR private var openRectInfoDraw = false + private var irCameraHelper: DualCameraHelper? = null + private var irFaceRectTransformer: FaceRectTransformer? = null + private fun initArcViewModel() { recognizeViewModel.setLiveType(livenessType) @@ -427,15 +442,18 @@ class SecondaryScreenPresentation( }) recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? -> Timber.i("recognizeNotice observe notice = $notice") - if (currentStep == 3 && notice == "leave") { -// step1() - } }) - recognizeViewModel.recognizeUserId.observe(activity, Observer { userId: String? -> - Timber.i("recognizeUserId observe userId = $userId") + recognizeViewModel.recognizeUserId.observe( + activity, + Observer { compareResult: CompareResult -> + Timber.i("recognizeUserId observe compareResult = $compareResult") + faceTime = System.currentTimeMillis() + recognitionWeight = lastWeight + lastFaceTrackId = compareResult.trackId + val faceEntity = compareResult.faceEntity + val userId = faceEntity.userName if (userId == null) return@Observer - viewModel.getUserNutritionData(userId = userId, foodId = currentFood!!.id!!) }) @@ -519,12 +537,13 @@ class SecondaryScreenPresentation( displayOrientation: Int, isMirror: Boolean ) { + Timber.d("Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") activity.runOnUiThread({ val previewSizeRgb = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( binding.dualCameraTexturePreviewRgb, binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, - previewSizeRgb, 90, 1f + previewSizeRgb, displayOrientation, 1f ) Timber.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") Timber.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") @@ -586,7 +605,7 @@ class SecondaryScreenPresentation( ) ) .rotation(activity.windowManager.defaultDisplay.rotation) - .additionalRotation(90) // 角度 + .additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度 .previewSize(recognizeViewModel.loadPreviewSize()) .specificCameraId(previewConfig.rgbCameraId) .isMirror(true) @@ -597,12 +616,96 @@ class SecondaryScreenPresentation( rgbCameraHelper!!.start() } + /** + * 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用 + */ + private fun initIrCamera() { + Timber.d("initIrCamera: livenessType = $livenessType") + if (livenessType === LivenessType.RGB) { + return + } + val irCameraListener: CameraListener = object : CameraListener { + override fun onCameraOpened( + camera: Camera, + cameraId: Int, + displayOrientation: Int, + isMirror: Boolean + ) { + Timber.d("IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") + val previewSizeIr = camera.getParameters().getPreviewSize() + val layoutParams = adjustPreviewViewSize( + binding.dualCameraTexturePreviewRgb, + binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr, + previewSizeIr, displayOrientation, 0.25f + ) + + irFaceRectTransformer = FaceRectTransformer( + previewSizeIr.width, previewSizeIr.height, + layoutParams.width, layoutParams.height, displayOrientation, cameraId, isMirror, + ConfigUtil.isDrawIrRectHorizontalMirror(context), + ConfigUtil.isDrawIrRectVerticalMirror(context) + ) + + recognizeViewModel.onIrCameraOpened(camera) + recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer) + } + + + override fun onPreview(nv21: ByteArray?, camera: Camera?) { + recognizeViewModel.refreshIrPreviewData(nv21) + } + + override fun onCameraClosed() { + Timber.i("onCameraClosed: ") + } + + override fun onCameraError(e: java.lang.Exception) { + Timber.i("onCameraError: ${e.message}") + e.printStackTrace() + } + + override fun onCameraConfigurationChanged( + cameraID: Int, + displayOrientation: Int + ) { + if (irFaceRectTransformer != null) { + irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation + } + Timber.i("onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation") + } + } + + val previewConfig = recognizeViewModel.previewConfig + irCameraHelper = DualCameraHelper.Builder() + .previewViewSize( + Point( + binding.dualCameraTexturePreviewIr.measuredWidth, + binding.dualCameraTexturePreviewIr.measuredHeight + ) + ) + .rotation(activity.windowManager.defaultDisplay.rotation) + .specificCameraId(previewConfig.irCameraId) + .previewOn(binding.dualCameraTexturePreviewIr) + .cameraListener(irCameraListener) + .isMirror(true) + .previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小 + .additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度 + .build() + irCameraHelper!!.init() + try { + irCameraHelper!!.start() + } catch (e: RuntimeException) { + ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice)) + } + } + /** * 绘制RGB、IR画面的实时人脸信息 * * @param facePreviewInfoList RGB画面的实时人脸信息 */ private fun drawPreviewInfo(facePreviewInfoList: MutableList) { +// Timber.d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}") if (rgbFaceRectTransformer != null) { val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( facePreviewInfoList, @@ -612,6 +715,27 @@ class SecondaryScreenPresentation( // 识别成功 binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) } + + if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) { + if (lastFaceTrackId != 0) { + Timber.i("$lastFaceTrackId 用户离开") + lastFaceTrackId = 0 + postUserData() + step1() + } + } + } + + fun postUserData() { + val userNutritionParam = UserNutritionParam( + userId = userNutritionData?.userId!!, + foodId = currentFood?.id!!, + faceTime = faceTime, + faceEndTime = System.currentTimeMillis(), + eatWeight = recognitionWeight - lastWeight, + foodWeight = lastWeight + ) + viewModel.postUserNutritionData(listOf(userNutritionParam)) } fun openRectInfoDraw(view: View?) { @@ -643,7 +767,10 @@ class SecondaryScreenPresentation( rgbCameraHelper!!.release() rgbCameraHelper = null } - SensorScaleUtils.closeScale() + if (irCameraHelper != null) { + irCameraHelper!!.release() + irCameraHelper = null + } recognizeViewModel.destroy() super.onStop() } diff --git a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt index fcc6b61..a7d693a 100644 --- a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt +++ b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt @@ -99,7 +99,7 @@ class RemoteRepository constructor( * 提交就餐数据 */ suspend fun postUserNutritionData( - param: UserNutritionParam + param: List ): ApiResponse { return safeApiCall { apiService.postUserNutritionData(param = param) } } diff --git a/app/src/main/java/com/sw/dualscreen/sdk/SensorScaleUtils.kt b/app/src/main/java/com/sw/dualscreen/sdk/SensorScaleUtils.kt index 0443989..d371f6a 100644 --- a/app/src/main/java/com/sw/dualscreen/sdk/SensorScaleUtils.kt +++ b/app/src/main/java/com/sw/dualscreen/sdk/SensorScaleUtils.kt @@ -1,6 +1,5 @@ package com.sw.dualscreen.sdk -import android.util.Log import com.sw.plate.utils.ToastUtils import com.wabon.wbintelligenthardwaresdk.api.SensorScale import com.wabon.wbintelligenthardwaresdk.api.SensorScale.OnScaleResult @@ -20,7 +19,9 @@ object SensorScaleUtils { private var mSensorScale: SensorScale? = null private var isOpened: Boolean = false private var isRead: Boolean = false + private var isZero = false private var callback: Callback? = {} + private var lastWeight: Double? = null private fun init() { mSensorScale = SensorScale(object : OnScaleResult { @@ -36,7 +37,8 @@ object SensorScaleUtils { } // Log.d(TAG, "readWeight state = ${stateStr}, weight = $value") // 只使用稳定值 - if (state == SensorScale.STATE_STABLE) { + if (state == SensorScale.STATE_STABLE && lastWeight != value) { + lastWeight = value callback?.invoke(value) } } @@ -45,11 +47,11 @@ object SensorScaleUtils { * 读取鉴别率 */ override fun readIdentify(rate: Int) { - Log.e(TAG, "readIdentify rate = $rate") + Timber.e("readIdentify rate = $rate") } override fun fail(errCode: Int) { - Log.e(TAG, "fail code = $errCode") + Timber.e("fail code = $errCode") } }) } @@ -66,9 +68,6 @@ object SensorScaleUtils { } this.callback = callback init() - mSensorScale?.zero { - Timber.d("startScale: zero") - } mSensorScale?.openScale(serialPort, baudRate) { open -> isOpened = open Timber.d("isOpened = $isOpened") @@ -78,6 +77,7 @@ object SensorScaleUtils { Thread.sleep(1000) // 打开后需要等待后才能调用,否则会 1001 SDK未初始化 mSensorScale?.startContinuousRead() + zero() }).start() } } @@ -88,7 +88,7 @@ object SensorScaleUtils { * 开启自动读取重量 */ fun startContinuousRead(callback: Callback?) { - Log.d(TAG, "开启自动读取 = $isRead") + Timber.d("开启自动读取 = $isRead") if (isRead) { return } @@ -101,7 +101,7 @@ object SensorScaleUtils { * 手动读取重量 */ fun readWeight(callback: Callback?) { - Log.d(TAG, "isOpened = $isOpened") + Timber.d("isOpened = $isOpened") if (!isOpened) { return } @@ -113,9 +113,11 @@ object SensorScaleUtils { * 零位标定 */ fun zero() { - if (!isOpened) return + Timber.d("zero isZero = $isZero") + if (isZero) return + isZero = true mSensorScale?.zero { - Log.d(TAG, "零位标定操作成功") + Timber.d("零位标定操作成功") ToastUtils.showToast("零位标定操作成功") } } @@ -126,7 +128,7 @@ object SensorScaleUtils { fun tare() { if (!isOpened) return mSensorScale?.tare { - Log.d(TAG, "去皮置零操作成功") + Timber.d("去皮置零操作成功") ToastUtils.showToast("去皮置零操作成功") } } @@ -135,7 +137,7 @@ object SensorScaleUtils { * 停止自动读取重量 */ fun stopContinuousRead() { - Log.d(TAG, "isOpened = $isOpened") + Timber.d("isOpened = $isOpened") if (!isOpened) { return } @@ -147,7 +149,7 @@ object SensorScaleUtils { * 关闭称重 */ fun closeScale() { - Log.d(TAG, "closeScale") + Timber.d("closeScale") isOpened = false mSensorScale?.closeScale() mSensorScale == null diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt index b7c0822..e056706 100644 --- a/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt @@ -68,7 +68,6 @@ abstract class BaseViewModel() : ViewModel() { GlobalData.restId = equipmentInfo.canteenId!! } - fun handleError(exception: Exception) { Timber.d("handleError ${exception.message}") } diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt index a22bad5..17fa65c 100644 --- a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt @@ -40,7 +40,7 @@ class UserViewModel : BaseViewModel() { val nutritionData: StateFlow = _nutritionData /** - * 饭点类型 + * 饭点类型 早餐/午餐/晚餐 */ private val _dinnerTypeInfo = MutableStateFlow(null) val dinnerTypeInfo: StateFlow = _dinnerTypeInfo @@ -201,9 +201,9 @@ class UserViewModel : BaseViewModel() { /** * 提交就餐数据 */ - fun postUserNutritionData(param: UserNutritionParam) { + fun postUserNutritionData(param: List) { Timber.d("postUserNutritionData param = $param") - launchWithLoading { + launch { val response = repository.postUserNutritionData(param) if (parseResponse(response)) { diff --git a/app/src/main/res/layout/presentation_secondary_screen.xml b/app/src/main/res/layout/presentation_secondary_screen.xml index f984378..fabd76a 100644 --- a/app/src/main/res/layout/presentation_secondary_screen.xml +++ b/app/src/main/res/layout/presentation_secondary_screen.xml @@ -16,7 +16,7 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:paddingTop="1dp" - android:visibility="gone"> + android:visibility="visible"> - + + + + + + + + + android:visibility="gone" /> 档口双屏 %s 初始化失败,错误码:%d\n错误码常量名:%s 权限被拒绝! + \n可能的原因:该设备不支持同时打开两个摄像头 即放即取 diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/camera/DualCameraHelper.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/camera/DualCameraHelper.java index 3c11b64..6e4b092 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/camera/DualCameraHelper.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/camera/DualCameraHelper.java @@ -129,7 +129,8 @@ public class DualCameraHelper implements Camera.PreviewCallback { //相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置 mCameraId = Camera.getNumberOfCameras() - 1; //若指定了相机ID且该相机存在,则打开指定的相机 - if (specificCameraId != null && specificCameraId <= mCameraId) { +// if (specificCameraId != null && specificCameraId <= mCameraId) { + if (specificCameraId != null) { mCameraId = specificCameraId; } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java index 7178ca1..65989b5 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java @@ -158,7 +158,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback { private MutableLiveData drawRectInfoText = new MutableLiveData<>(); - private MutableLiveData recognizeUserId = new MutableLiveData<>(); + private MutableLiveData recognizeUserId = new MutableLiveData<>(); /** * 检测ir活体前,是否需要更新faceData @@ -475,7 +475,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback { Disposable disposable = Observable.just(true).observeOn(AndroidSchedulers.mainThread()).subscribe(aBoolean -> { if (similarPass) { if (recognizeUserId != null) { - recognizeUserId.postValue(compareResult.getFaceEntity().getUserName()); + recognizeUserId.postValue(compareResult); } boolean isAdded = false; List compareResults = compareResultList.getValue(); @@ -541,7 +541,7 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback { return recognizeNotice; } - public MutableLiveData getRecognizeUserId() { + public MutableLiveData getRecognizeUserId() { return recognizeUserId; }