【OpenHarmony/HarmonyOs 】CheckMe 传感器与触觉反馈实践:加速度计、光线传感器、距离传感器和振动检测

本文基于 CheckMe 项目,聚焦 HarmonyOS 设备感知能力:传感器、音频采集播放、振动马达与硬件检测交互。这个方向和前面的权限、服务卡片、端侧 AI、实况窗等文章不重复,更适合写“鸿蒙设备能力实战”。

HarmonyOS 的全场景能力不只是跨设备,也包括对设备硬件的细粒度调用。一个真正像样的设备检测工具,不能只显示 CPU 和内存,还应该能检测:

  • 扬声器是否正常
  • 麦克风是否有输入
  • 振动马达是否可用
  • 加速度计是否有数据
  • 陀螺仪是否正常
  • 光线传感器是否工作
  • 距离传感器是否响应

CheckMe 的硬件检测页就围绕这些能力做了比较完整的工程组织。🎛️


一、硬件检测页的设计目标

硬件检测不是简单调用 API,它要解决三个问题:

  1. 让用户知道正在检测什么。
  2. 让用户能感知检测过程。
  3. 检测失败时给出明确状态,而不是静默无响应。

因此项目中定义了统一的测试状态:

enum TestStatus {
  NOT_STARTED = 'not_started',
  TESTING = 'testing',
  PASSED = 'passed',
  FAILED = 'failed',
  UNAVAILABLE = 'unavailable'
}

这套状态可以覆盖大多数硬件检测场景:

  • TESTING:正在监听、播放、定位或采集
  • PASSED:检测通过
  • FAILED:能力存在但结果异常
  • UNAVAILABLE:设备不支持或 API 不可用

统一状态模型让页面 UI、Toast 提示、检测结果列表都能复用同一套逻辑。


二、扬声器测试:用 AudioRenderer 播放正弦波

HardwareTest.ets 中,扬声器测试使用 AudioRenderer 播放 PCM 数据。核心代码如下:

const audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
  channels: audio.AudioChannel.CHANNEL_2,
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE
};

const audioRendererOptions: audio.AudioRendererOptions = {
  streamInfo: audioStreamInfo,
  rendererInfo: {
    usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
    rendererFlags: 0
  }
};

this.audioRenderer = await audio.createAudioRenderer(audioRendererOptions);
await this.audioRenderer.start();

项目没有依赖外部音频文件,而是直接生成正弦波:

const sampleRate = 44100;
const durationSec = 2;
const numSamples = sampleRate * durationSec;
const buffer: ArrayBuffer = new ArrayBuffer(numSamples * 2);
const view = new Int16Array(buffer);

for (let i = 0; i < numSamples; i++) {
  const t = i / sampleRate;
  const sample = Math.sin(2 * Math.PI * this.speakerFrequency * t) * 8000;
  view[i] = Math.max(-32768, Math.min(32767, Math.round(sample)));
}

这样做有几个优点:

  • 不需要打包音频资源
  • 可以动态调整频率
  • 可以测试左右声道
  • 更适合硬件检测场景

📌 对设备检测工具来说,生成测试信号比播放固定音频更灵活。


三、麦克风测试:用 AudioCapturer 实时读取音量

麦克风检测使用 AudioCapturer

const audioCapturerOptions: audio.AudioCapturerOptions = {
  streamInfo: {
    samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
    channels: audio.AudioChannel.CHANNEL_1,
    encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW,
    sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE
  },
  capturerInfo: {
    source: audio.SourceType.SOURCE_TYPE_MIC,
    capturerFlags: 0
  }
};

this.audioCapturer = await audio.createAudioCapturer(audioCapturerOptions);
await this.audioCapturer.start();

然后循环读取音频数据,计算平均音量:

const buffer: ArrayBuffer = await this.audioCapturer.read(bufferSize, true);
const view = new Int16Array(buffer);
let sum = 0;

for (let i = 0; i < view.length; i++) {
  sum = sum + Math.abs(view[i]);
}

const avg = sum / view.length;
this.micVolume = Math.min(100, Math.round(avg / 327.68));

这段代码把底层 PCM 数据转换成用户能理解的“音量百分比”。如果 micVolume > 5,就认为检测到声音:

if (this.micVolume > 5) {
  this.updateResult('麦克风测试', TestStatus.PASSED, '检测到声音');
} else {
  this.updateResult('麦克风测试', TestStatus.FAILED, '未检测到声音');
}

这比简单“能否打开麦克风”更有意义,因为它真的检测了输入数据。


四、振动检测:先判断支持,再触发触觉反馈

振动马达检测使用 @ohos.vibrator

private async startVibratorTest(): Promise<void> {
  this.vibratorTesting = true;
  this.updateResult('振动马达', TestStatus.TESTING, '振动中...');

  const isSupported: boolean = vibrator.isHdHapticSupported();
  if (!isSupported) {
    this.updateResult('振动马达', TestStatus.UNAVAILABLE, '设备不支持振动');
    this.vibratorTesting = false;
    return;
  }

  const vibrateTime: vibrator.VibrateTime = {
    type: 'time',
    duration: 500
  };
  const vibrateAttr: vibrator.VibrateAttribute = {
    usage: 'touch'
  };

  await vibrator.startVibration(vibrateTime, vibrateAttr);
}

这里的顺序很关键:

  1. 先设置测试状态。
  2. 再判断设备是否支持。
  3. 支持才触发振动。
  4. 不支持时返回 UNAVAILABLE

这种写法比直接调用 startVibration() 更稳,因为不同设备形态对振动支持情况可能不同。


五、加速度计:实时监听三轴数据

加速度计检测使用 Sensor API 监听三轴数据:

sensor.on(
  sensor.SensorType.SENSOR_TYPE_ID_ACCELEROMETER,
  (data: sensor.AccelerometerResponse) => {
    this.sensorAccelerometer = {
      x: Math.round(data.x * 100) / 100,
      y: Math.round(data.y * 100) / 100,
      z: Math.round(data.z * 100) / 100
    };

    this.updateResult(
      '加速度计',
      TestStatus.PASSED,
      'X:' + this.sensorAccelerometer.x.toFixed(2) +
      ' Y:' + this.sensorAccelerometer.y.toFixed(2) +
      ' Z:' + this.sensorAccelerometer.z.toFixed(2) + ' m/s²'
    );
  },
  { interval: 100000000 }
);

用户移动设备时,三轴数据会变化。页面就能给出“监听中”“检测通过”“无数据”等状态。

停止时要记得注销监听:

sensor.off(sensor.SensorType.SENSOR_TYPE_ID_ACCELEROMETER);

传感器类能力一定要注意释放,否则页面切换后仍然监听,会造成不必要的资源消耗。


六、陀螺仪:检测旋转角速度

陀螺仪和加速度计类似,但它检测的是旋转角速度:

sensor.on(
  sensor.SensorType.SENSOR_TYPE_ID_GYROSCOPE,
  (data: sensor.GyroscopeResponse) => {
    this.sensorGyroscope = {
      x: Math.round(data.x * 100) / 100,
      y: Math.round(data.y * 100) / 100,
      z: Math.round(data.z * 100) / 100
    };

    this.updateResult(
      '陀螺仪',
      TestStatus.PASSED,
      'X:' + this.sensorGyroscope.x.toFixed(2) +
      ' Y:' + this.sensorGyroscope.y.toFixed(2) +
      ' Z:' + this.sensorGyroscope.z.toFixed(2) + ' rad/s'
    );
  },
  { interval: 100000000 }
);

它适合检测:

  • 设备旋转
  • 姿态变化
  • 横竖屏辅助判断
  • 游戏和运动场景输入

CheckMe 中,它作为硬件检测项出现,用户旋转设备即可看到数据变化。


七、光线传感器:让“沉浸光感”有真实硬件依据

光线传感器检测代码:

sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data: sensor.LightResponse) => {
  this.sensorLight = Math.round(data.intensity * 100) / 100;
  this.updateResult(
    '光线传感器',
    TestStatus.PASSED,
    '强度: ' + this.sensorLight.toFixed(1) + ' lux'
  );
}, { interval: 500000000 });

光线传感器返回的是环境光强度,单位常用 lux。

这类数据除了做硬件检测,还可以扩展为:

  • 根据环境光调整图表亮度
  • 弱光环境自动降低页面对比度
  • 强光环境提高关键文字可读性
  • 结合深色模式做更自然的视觉体验

这就把“沉浸光感”从 UI 风格变成了硬件能力驱动。


八、距离传感器:检测贴近状态

距离传感器代码:

sensor.on(sensor.SensorId.PROXIMITY, (data: sensor.ProximityResponse) => {
  this.sensorProximity = data.distance;
  this.updateResult(
    '距离传感器',
    TestStatus.PASSED,
    '距离: ' + this.sensorProximity.toFixed(2) + ' cm'
  );
}, { interval: 500000000 });

距离传感器可以用于:

  • 通话贴耳检测
  • 防误触
  • 口袋模式
  • 设备状态判断

在工具类应用中,它可以作为硬件自检项,帮助用户确认传感器是否工作正常。


九、硬件检测的资源释放策略

项目中切换模块时会停止上一个模块的资源:

private switchModule(module: TestModule): void {
  if (this.currentModule === TestModule.SPEAKER) {
    this.stopSpeakerTest();
  } else if (this.currentModule === TestModule.MICROPHONE) {
    this.stopMicrophoneTest();
  } else if (this.currentModule === TestModule.SENSOR) {
    this.stopSensorTests();
  } else if (this.currentModule === TestModule.GPS) {
    this.stopGpsTest();
  }

  this.currentModule = module;
  this.isFullScreen = false;
}

这是硬件类功能非常重要的工程习惯:

  • 音频播放要 release
  • 麦克风采集要 stop
  • 传感器监听要 off
  • 振动要 stop
  • 定位要停止或避免重复请求

否则应用可能出现耗电、占用硬件资源、页面切走后仍有回调等问题。


十、文章小结

CheckMe 的硬件检测页把 HarmonyOS 设备能力串成了一套完整体验:

  • AudioRenderer:扬声器测试
  • AudioCapturer:麦克风测试
  • vibrator:触觉反馈检测
  • sensor:加速度计、陀螺仪、光线、距离传感器
  • 统一 TestStatus:管理检测状态
  • 资源释放:保证切换模块后不残留监听

这类文章很适合突出 HarmonyOS 的系统能力,因为它不是单纯 UI 页面,而是真正调用了设备硬件。

最后一句总结:硬件检测工具的价值,不只是告诉用户“设备有什么”,而是让用户真实感知“设备能力是否可用”。 🔧


参考资料

img

img

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐