diff --git a/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseAccelerometerWake.kt b/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseAccelerometerWake.kt index c464c45..a43ce97 100644 --- a/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseAccelerometerWake.kt +++ b/app/src/main/java/com/xiaoqu/watch/device/sensor/FiseAccelerometerWake.kt @@ -18,13 +18,16 @@ import javax.inject.Singleton * - 方案D:TYPE_WRIST_TILT_GESTURE(type=26),系统级手势识别,最省电最准确 * - 方案C:TYPE_ACCELEROMETER Z轴变化趋势检测,作为降级方案 * - * 方案C核心逻辑: - * 检测Z轴从低值(<3,手臂下垂)到高值(≥6,手臂抬起)的变化趋势。 - * 与旧版简单的Z≥5判断相比,可防止: - * - 手表平放桌面常亮(Z一直≈9.8,无"低→高"变化) - * - 走路手臂摆动频繁亮灭(Z波动但无持续趋势) + * 方案C核心逻辑(v3 最小值跳变检测): + * 追踪近期5个采样(≈1秒)的最小Z值。当近期有低值(<5,手臂曾下垂) + * 且当前Z值高(≥7,手臂已抬起)时判定为抬手。 + * 只需1个低值采样即可触发,比均值方案灵敏得多。 + * 触发后2秒冷却期防连续触发。 * - * 参数来源:baseline/04 — Z轴唤醒阈值≥5(home.vue:122),加迟滞调整为低<3/高≥6 + * 与旧版和v1/v2方案的改进: + * - 旧版Z≥5:平放桌面常亮、走路闪烁 + * - v1/v2均值对比:需要连续3个低值,抬手动作太快时漏触发 + * - v3最小值:只需1个低值,更符合真实抬手动作的数据特征 */ @Singleton class FiseAccelerometerWake @Inject constructor( @@ -33,14 +36,14 @@ class FiseAccelerometerWake @Inject constructor( ) : AccelerometerWakeController, SensorEventListener { companion object { - /** 方案C:Z轴低位阈值(手臂下垂判定),实测下垂稳定值0-3 */ - private const val Z_LOW_THRESHOLD = 4f - /** 方案C:Z轴高位阈值(手臂抬起判定),实测抬手过渡期均值4.5-6.5,稳定值8-9 */ - private const val Z_HIGH_THRESHOLD = 5f - /** 方案C:滑动窗口大小(前半+后半各3个采样,SENSOR_DELAY_NORMAL下约1.2秒) */ - private const val WINDOW_SIZE = 6 - /** 方案C:前半窗口大小 */ - private const val HALF_WINDOW = WINDOW_SIZE / 2 + /** 方案C:近期最小值窗口大小(5个采样≈1秒,用于追踪"手臂曾经放下过") */ + private const val MIN_WINDOW_SIZE = 5 + /** 方案C:最小值阈值,近期有采样低于此值才认为"手臂曾下垂" */ + private const val Z_MIN_THRESHOLD = 5f + /** 方案C:当前值阈值,超过此值认为"手臂已抬起" */ + private const val Z_CURRENT_THRESHOLD = 7f + /** 方案C:触发后冷却采样数(防连续触发,10个≈2秒) */ + private const val COOLDOWN_SAMPLES = 10 } private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager @@ -52,13 +55,15 @@ class FiseAccelerometerWake @Inject constructor( /** 是否已启动 */ private var started = false - // === 方案C:Z轴滑动窗口 === - /** 固定大小的环形缓冲区,存储最近的Z轴采样值 */ - private val zWindow = FloatArray(WINDOW_SIZE) + // === 方案C:最小值追踪 === + /** 环形缓冲区,存储最近N个Z轴采样值,用于找最小值 */ + private val zWindow = FloatArray(MIN_WINDOW_SIZE) { Float.MAX_VALUE } /** 当前写入位置 */ private var windowIndex = 0 - /** 窗口是否已填满(至少6个采样后才开始判断) */ + /** 窗口是否已填满 */ private var windowFilled = false + /** 触发后冷却计数器 */ + private var cooldownCount = 0 /** * 开始监听传感器 @@ -133,51 +138,46 @@ class FiseAccelerometerWake @Inject constructor( // === 方案C 核心逻辑 === /** - * Z轴变化趋势检测 - * 用环形缓冲区存储最近6个Z轴采样值,分前半(旧)和后半(新)各3个: - * - 前3个均值 < 3(手臂下垂状态) - * - 后3个均值 ≥ 6(手臂抬起状态) - * 满足条件 = 检测到"抬手"动作 + * 最小值跳变检测 + * 追踪近期(最近5个采样≈1秒)的最小Z值: + * - 如果近期最小值 < 5(说明手臂曾经放下过) + * - 并且当前Z值 ≥ 7(说明手臂已抬起) + * → 判定为"抬手"动作 + * + * 优点:只需一个低值采样即可触发,比均值方案灵敏得多 */ private fun detectWristRaise(z: Float) { + // 冷却期:刚触发过,等待冷却结束 + if (cooldownCount > 0) { + cooldownCount-- + return + } + // 写入环形缓冲区 - zWindow[windowIndex % WINDOW_SIZE] = z + val pos = windowIndex % MIN_WINDOW_SIZE + zWindow[pos] = z windowIndex++ - // 窗口未填满,等待更多采样 - if (!windowFilled) { - if (windowIndex >= WINDOW_SIZE) { - windowFilled = true - } else { - return - } + if (!windowFilled && windowIndex >= MIN_WINDOW_SIZE) { + windowFilled = true } + if (!windowFilled) return - // 计算前半(较旧的3个采样)和后半(较新的3个采样)的均值 - // 环形缓冲区中,当前写入位置的前 HALF_WINDOW 个是最新的,再前 HALF_WINDOW 个是较旧的 - val currentPos = windowIndex % WINDOW_SIZE - var oldSum = 0f - var newSum = 0f - for (i in 0 until HALF_WINDOW) { - // 较旧的3个:从 currentPos 往前数第 6,5,4 个位置 - val oldIdx = (currentPos - WINDOW_SIZE + i + WINDOW_SIZE) % WINDOW_SIZE - // 较新的3个:从 currentPos 往前数第 3,2,1 个位置 - val newIdx = (currentPos - HALF_WINDOW + i + WINDOW_SIZE) % WINDOW_SIZE - oldSum += zWindow[oldIdx] - newSum += zWindow[newIdx] + // 找近期最小值 + var recentMin = Float.MAX_VALUE + for (i in 0 until MIN_WINDOW_SIZE) { + if (zWindow[i] < recentMin) recentMin = zWindow[i] } - val oldAvg = oldSum / HALF_WINDOW - val newAvg = newSum / HALF_WINDOW // DEBUG: 输出每次计算结果,用于调参(正式发布时删除) - Timber.v("抬手亮屏: Z=%.1f oldAvg=%.1f newAvg=%.1f (阈值: old<%.0f AND new>=%.0f)", - zWindow[(windowIndex - 1 + WINDOW_SIZE) % WINDOW_SIZE], - oldAvg, newAvg, Z_LOW_THRESHOLD, Z_HIGH_THRESHOLD) + Timber.v("抬手亮屏: Z=%.1f recentMin=%.1f (阈值: min<%.0f AND cur>=%.0f)", + z, recentMin, Z_MIN_THRESHOLD, Z_CURRENT_THRESHOLD) - // 判断:从低位(下垂)到高位(抬起)的变化趋势 - if (oldAvg < Z_LOW_THRESHOLD && newAvg >= Z_HIGH_THRESHOLD) { + // 判断:近期有低值(手臂曾下垂)且当前值高(手臂已抬起) + if (recentMin < Z_MIN_THRESHOLD && z >= Z_CURRENT_THRESHOLD) { wakeScreenIfOff() - // 触发后清空窗口,防止连续触发 + // 触发后进入冷却期 + 清空窗口 + cooldownCount = COOLDOWN_SAMPLES resetWindow() } } @@ -190,10 +190,10 @@ class FiseAccelerometerWake @Inject constructor( } } - /** 重置滑动窗口 */ + /** 重置窗口 */ private fun resetWindow() { windowIndex = 0 windowFilled = false - zWindow.fill(0f) + zWindow.fill(Float.MAX_VALUE) } }