当前位置: 首页 > article >正文

HarmonyOS运动开发:精准估算室内运动的距离、速度与步幅

##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)#

前言

在室内运动场景中,由于缺乏 GPS 信号,传统的基于卫星定位的运动数据追踪方法无法使用。因此,如何准确估算室内运动的距离、速度和步幅,成为了运动应用开发中的一个重要挑战。本文将结合鸿蒙(HarmonyOS)开发实战经验,深入解析如何利用加速度传感器等设备功能,实现室内运动数据的精准估算。

一、加速度传感器:室内运动数据的核心

加速度传感器是实现室内运动数据估算的关键硬件。它能够实时监测设备在三个轴向上的加速度变化,从而为运动状态分析提供基础数据。以下是加速度传感器服务类的核心代码:

import common from '@ohos.app.ability.common';
import sensor from '@ohos.sensor';
import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { UserProfile } from '../user/UserProfile';interface Accelerometer {x: number;y: number;z: number;
}export class AccelerationSensorService {private static instance: AccelerationSensorService | null = null;private context: common.UIAbilityContext;private isMonitoring: boolean = false; // 是否正在监听private constructor(context: common.UIAbilityContext) {this.context = context;}static getInstance(context: common.UIAbilityContext): AccelerationSensorService {if (!AccelerationSensorService.instance) {AccelerationSensorService.instance = new AccelerationSensorService(context);}return AccelerationSensorService.instance;}private accelerometerCallback = (data: sensor.AccelerometerResponse) => {this.accelerationData = {x: data.x,y: data.y,z: data.z};};private async requestAccelerationPermission(): Promise<boolean> {const atManager = abilityAccessCtrl.createAtManager();try {const result = await atManager.requestPermissionsFromUser(this.context,['ohos.permission.ACCELEROMETER']);return result.permissions[0] === 'ohos.permission.ACCELEROMETER' &&result.authResults[0] === 0;} catch (err) {console.error('申请权限失败:', err);return false;}}public async startDetection(): Promise<void> {if (this.isMonitoring) return;const hasPermission = await this.requestAccelerationPermission();if (!hasPermission) {throw new Error('未授予加速度传感器权限');}this.isMonitoring = true;this.setupAccelerometer();}private setupAccelerometer(): void {try {sensor.on(sensor.SensorId.ACCELEROMETER, this.accelerometerCallback);console.log('加速度传感器启动成功');} catch (error) {console.error('加速度传感器初始化失败:', (error as BusinessError).message);}}public stopDetection(): void {if (!this.isMonitoring) return;this.isMonitoring = false;sensor.off(sensor.SensorId.ACCELEROMETER, this.accelerometerCallback);}private accelerationData: Accelerometer = { x: 0, y: 0, z: 0 };getCurrentAcceleration(): Accelerometer {return this.accelerationData;}calculateStride(timeDiff: number): number {const accel = this.accelerationData;const magnitude = Math.sqrt(accel.x ** 2 + accel.y ** 2 + accel.z ** 2);const userProfile = UserProfile.getInstance();if (Math.abs(magnitude - 9.8) < 0.5) { // 接近重力加速度时视为静止return 0;}const baseStride = userProfile.getHeight() * 0.0045; // 转换为米const dynamicFactor = Math.min(1.5, Math.max(0.8, (magnitude / 9.8) * (70 / userProfile.getWeight())));return baseStride * dynamicFactor * timeDiff;}
}

核心点解析

• 权限申请:在使用加速度传感器之前,必须申请ohos.permission.ACCELEROMETER权限。通过abilityAccessCtrl.createAtManager方法申请权限,并检查用户是否授权。

• 数据监听:通过sensor.on方法监听加速度传感器数据,实时更新accelerationData

• 步幅计算:结合用户身高和加速度数据动态计算步幅。静止状态下返回 0 步幅,避免误判。

二、室内运动数据的估算

在室内运动场景中,我们无法依赖 GPS 定位,因此需要通过步数和步幅来估算运动距离和速度。以下是核心计算逻辑:

addPointBySteps(): number {const currentSteps = this.stepCounterService?.getCurrentSteps() ?? 0;const userProfile = UserProfile.getInstance();const accelerationService = AccelerationSensorService.getInstance(this.context);const point = new RunPoint(0, 0);const currentTime = Date.now();point.netDuration = Math.floor((currentTime - this.startTime) / 1000);point.totalDuration = point.netDuration + Math.floor(this.totalDuration);const pressureService = PressureDetectionService.getInstance();point.altitude = pressureService.getCurrentAltitude();point.totalAscent = pressureService.getTotalAscent();point.totalDescent = pressureService.getTotalDescent();point.steps = currentSteps;if (this.runState === RunState.Running) {const stepDiff = currentSteps - (this.previousPoint?.steps ?? 0);const timeDiff = (currentTime - (this.previousPoint?.timestamp ?? currentTime)) / 1000;const accelData = accelerationService.getCurrentAcceleration();const magnitude = Math.sqrt(accelData.x ** 2 + accelData.y ** 2 + accelData.z ** 2);let stride = accelerationService.calculateStride(timeDiff);if (stepDiff > 0 && stride > 0) {const distanceBySteps = stepDiff * stride;this.totalDistance += distanceBySteps / 1000;point.netDistance = this.totalDistance * 1000;point.totalDistance = point.netDistance;console.log(`步数变化: ${stepDiff}, 步幅: ${stride.toFixed(2)}m, 距离增量: ${distanceBySteps.toFixed(2)}m`);}if (this.previousPoint && timeDiff > 0) {const instantCadence = stepDiff > 0 ? (stepDiff / timeDiff) * 60 : 0;point.cadence = this.currentPoint ?(this.currentPoint.cadence * 0.7 + instantCadence * 0.3) :instantCadence;const instantSpeed = distanceBySteps / timeDiff;point.speed = this.currentPoint ?(this.currentPoint.speed * 0.7 + instantSpeed * 0.3) :instantSpeed;point.stride = stride;} else {point.cadence = this.currentPoint?.cadence ?? 0;point.speed = this.currentPoint?.speed ?? 0;point.stride = stride;}if (this.exerciseType && userProfile && this.previousPoint) {const distance = point.netDuration;const ascent = point.totalAscent - this.previousPoint.totalAscent;const descent = point.totalDescent - this.previousPoint.totalDescent;const newCalories = CalorieCalculator.calculateCalories(this.exerciseType,userProfile.getWeight(),userProfile.getAge(),userProfile.getGender(),0, // 暂不使用心率数据ascent,descent,distance);point.calories = this.previousPoint.calories + newCalories;}}this.previousPoint = this.currentPoint;this.currentPoint = point;if (this.currentSport && this.runState === RunState.Running) {this.currentSport.distance = this.totalDistance * 1000;this.currentSport.calories = point.calories;this.sportDataService.saveCurrentSport(this.currentSport);}return this.totalDistance;
}

核心点解析

• 步数差与时间差:通过当前步数与上一次记录的步数差值,结合时间差,计算出步频和步幅。

• 动态步幅调整:根据加速度数据动态调整步幅,确保在不同运动强度下的准确性。

• 速度与卡路里计算:结合步幅和步数差值,计算出运动速度和消耗的卡路里。

• 数据平滑处理:使用移动平均法对步频和速度进行平滑处理,减少数据波动。

三、每秒更新数据

为了实时展示运动数据,我们需要每秒更新一次数据。以下是定时器的实现逻辑:

 private startTimer(): void {if (this.timerInterval === null) {this.timerInterval = setInterval(() => {if (this.runState === RunState.Running) {this.netDuration = Math.floor((Date.now() - this.startTime) / 1000);// 室内跑:使用步数添加轨迹点if (this.exerciseType?.sportType === SportType.INDOOR) {this.addPointBySteps(); // 新增调用}// 计算当前配速(秒/公里)let currentPace = 0;if (this.totalDistance > 0) {currentPace = Math.floor(this.netDuration / this.totalDistance);}if (this.currentPoint) {this.currentPoint.pace = currentPace;}// 通知所有监听器this.timeListeners.forEach(listener => {listener.onTimeUpdate(this.netDuration, this.currentPoint);});}}, 1000); // 每1秒更新一次}}

核心点解析

  1. 定时器设置:使用 setInterval 方法每秒触发一次数据更新逻辑。
  2. 运动状态判断:只有在运动状态为 Running 时,才进行数据更新。
  3. 配速计算:通过总时间与总距离的比值计算当前配速。
  4. 通知监听器:将更新后的数据通过监听器传递给其他组件,确保数据的实时展示。

四、优化与改进

1. 数据平滑处理

在实际运动过程中,加速度数据可能会受到多种因素的干扰,导致数据波动较大。为了提高数据的准确性和稳定性,我们采用了移动平均法对步频和速度进行平滑处理:

point.cadence = this.currentPoint ?(this.currentPoint.cadence * 0.7 + instantCadence * 0.3) :instantCadence;point.speed = this.currentPoint ?(this.currentPoint.speed * 0.7 + instantSpeed * 0.3) :instantSpeed;

通过这种方式,可以有效减少数据的短期波动,使运动数据更加平滑和稳定。

2.动态步幅调整

步幅会因用户的运动强度和身体状态而变化。为了更准确地估算步幅,我们引入了动态调整机制:

let stride = accelerationService.calculateStride(timeDiff);

calculateStride方法中,结合用户的身高、体重和加速度数据,动态计算步幅。这种方法可以更好地适应不同用户的运动状态。

五、总结与展望

通过加速度传感器和定时器,我们成功实现了室内运动的距离、速度和步幅估算。这些功能不仅能够帮助用户更好地了解自己的运动状态,还能为运动健康管理提供重要数据支持。

相关文章:

HarmonyOS运动开发:精准估算室内运动的距离、速度与步幅

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在室内运动场景中&#xff0c;由于缺乏 GPS 信号&#xff0c;传统的基于卫星定位的运动数据追踪方法无法使用。因此&#xff0c;如何准确估算室内运动的距离、速度和步幅&#xff0c;…...

Web攻防-SQL注入高权限判定跨库查询文件读写DNS带外SecurePriv开关绕过

知识点&#xff1a; 1、Web攻防-SQL注入-高权限用户差异 2、Web攻防-SQL注入-跨库注入&文件读写&DNS带外 案例说明&#xff1a; 在应用中&#xff0c;数据库用户不同&#xff0c;可操作的数据库和文件读写权限不一&#xff0c;所有在注入过程中可以有更多的利用思路&a…...

C语言数据结构笔记3:Union联合体+结构体取8位Bool量

本文衔接上文要求&#xff0c;新增8位bool量的获取方式。 目录 问题提出&#xff1a; Union联合体struct结构体(方式1)&#xff1a; Union联合体struct结构体(方式2)&#xff1a; BYTE方式读取&#xff1a; 问题提出&#xff1a; 在STM32单片机的编程中&#xff0c;无法定义Boo…...

深拷贝与浅拷贝的区别?如何手写实现一个深拷贝?

导语&#xff1a; “深拷贝 VS 浅拷贝”是前端面试中绕不开的经典问题&#xff0c;既能考察 JavaScript 基础功&#xff0c;又能延伸至手写代码、递归、循环引用处理等进阶话题。本文从面试官视角解析其考察重点&#xff0c;并详解如何手写一个实用的深拷贝函数&#xff0c;助你…...

grafana 批量视图备份及恢复(含数据源)

一、grafana 批量视图备份 import requests import json import urllib3 import osfrom requests.auth import HTTPBasicAuthfilename_folders_map "folders_map.json" type_folder "dash-folder" type_dashboard "dash-db"# Grafana服务器地…...

SAP学习笔记 - 开发22 - 前端Fiori开发 数据绑定(Jason),Data Types(数据类型)

上一章讲了Icons&#xff08;图标&#xff09;&#xff0c;Icon Explorer。 SAP学习笔记 - 开发21 - 前端Fiori开发 Icons&#xff08;图标&#xff09;&#xff0c;Icon Explorer&#xff08;图标浏览器&#xff09;-CSDN博客 本章继续讲SAP Fiori开发的知识。 目录 1&…...

网络编程之TCP编程

基于 C/S &#xff1a;客户端&#xff08;client&#xff09;/服务器端&#xff08;server&#xff09; 1.流程 2. 函数接口 所有函数所需头文件&#xff1a; #include <sys/types.h> #include <sys/socket.h> 系统定义好了用来存储网络信息的结构体 ipv4通信使…...

C++进阶--C++11(04)

文章目录 C进阶--C11&#xff08;04&#xff09;lambdalambda表达式语法捕捉列表lambda的应用lambda的原理 包装器functionbind 总结结语 很高兴和大家见面&#xff0c;给生活加点impetus&#xff01;&#xff01;开启今天的编程之路&#xff01;&#xff01; 今天我们进一步c…...

当AI遇上防火墙:新一代智能安全解决方案全景解析

在2025年网络安全攻防升级的背景下&#xff0c;AI与防火墙的融合正重塑安全防御体系。以下三款产品通过机器学习、行为分析等技术创新&#xff0c;为企业提供智能化主动防护&#xff1a; 1. 保旺达数据安全管控平台——AI驱动的动态治理引擎‌ 智能分类分级‌&#xff1a;基于…...

STL 库基础概念与示例

一、STL 库基础概念与示例 1. 容器分类 顺序容器 核心特性&#xff1a;按元素插入顺序存储&#xff0c;支持下标访问&#xff08;类似数组&#xff09;&#xff0c;动态扩展内存。典型容器&#xff1a;vector&#xff08;动态数组&#xff09;。适用场景&#xff1a;需要频繁…...

Spring MVC参数绑定终极手册:单多参/对象/集合/JSON/文件上传精讲

我们通过浏览器访问不同的路径&#xff0c;就是在发送不同的请求&#xff0c;在发送请求时&#xff0c;可能会带一些参数&#xff0c;本文将介绍了Spring MVC中处理不同请求参数的多种方式 一、传递单个参数 接收单个参数&#xff0c;在Spring MVC中直接用方法中的参数就可以&…...

Fluence推出“Pointless计划”:五种方式参与RWA算力资产新时代

2025年6月1日&#xff0c;去中心化算力平台 Fluence 正式宣布启动“Pointless 计划”——这是其《Fluence Vision 2026》战略中四项核心举措之一&#xff0c;旨在通过贡献驱动的积分体系&#xff0c;激励更广泛的社区参与&#xff0c;为用户带来现实世界资产&#xff08;RWA&am…...

innovus: ecoAddRepeater改变hier层级解决办法

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 来自星球提问: 星主&#xff0c;我在A/B/C/D/E/U0这个cell后面插入一个BUFF&#xff0c;生成的名字为A/B/C/BUFF1&#xff0c;少了D/E两个层级&#xff0c;不应该是生成A/B/C/…...

华为OD机试真题——硬件产品销售方案(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《硬件产品销售方案》: 目录…...

突破数据孤岛:StarRocks联邦查询实战指南

随着企业数据生态的复杂化&#xff0c;跨多个数据存储系统进行联合查询的需求日益增长。本文将深入解析如何利用StarRocks构建高效的数据联邦查询体系&#xff0c;实现与Apache Doris和Hive数据仓库的无缝对接。 ### 一、StarRocks联邦查询架构解析 StarRocks采用分布式架构设…...

传统业务对接AI-AI编程框架-Rasa的业务应用实战(1)--项目背景即学习初衷

我的初衷&#xff1a;我想学习AI。具体的方向是这样的&#xff1a;原本传统的平台业务去对接智能体。比如发票业务&#xff0c;发票的开具、审核、计税、回款等。根据用户在业务系统前台界面输入若干提示词 或者 语音输入简短语音信息&#xff0c;可以通过智能体给出需要处理的…...

低功耗架构突破:STM32H750 与 SD NAND (存储芯片)如何延长手环续航至 14 天

低功耗架构突破&#xff1a;STM32H750 与 SD NAND &#xff08;存储芯片&#xff09;如何延长手环续航至 14 天 卓越性能强化安全高效能效图形处理优势丰富集成特性 模拟模块实时监控保障数据完整性提升安全性与可靠性测量原理采样率相关结束语 在智能皮电手环及数据存储技术不…...

CSS选择子元素

通过选择器 为所有子元素应用样式。以下是几种常见方法&#xff1a; 1. 选择所有直接子元素&#xff08;不包括孙级&#xff09; css 复制 下载 .parent > * {/* 样式规则 */color: red; } > 选择器&#xff1a;只匹配直接子元素 * 通配符&#xff1a;匹配任意类型…...

git cherry-pick (28)

1.1 目的 本文档用于说明如何git上,通过cherry-pick命令合并某个功能。 将分支bg_device的 AHB New feature support libalgo arm64 lib 提交内容合并至 分支spfl_device 分支当中 1.2适配步骤 1.2.1 实操过程 > 分支sfpl_device的状态 rootxrootx-ThinkPad:~/workdir…...

android与Qt类比

一、概念对应关系 Android RecyclerView 组件类比描述Qt 模型 - 视图组件Qt 类比描述RecyclerView画板&#xff08;容器&#xff09;QAbstractItemView视图&#xff08;展示数据的容器&#xff0c;如列表、表格&#xff09;RecyclerView.Adapter画布&#xff08;数据桥梁&…...

AX513CE 是一款针对模组渠道市场前端IPC应用而设计的数字SOC芯片 支持高清CMOS Sensor输入 国产品牌

AX513CE 是一款针对模组渠道市场前端IPC应用而设计的数字SOC芯片 支持高清CMOS Sensor输入 国产品牌 产品概述&#xff1a; AX513CE 是一款针对模组渠道市场前端IPC应用而设计的数字SOC芯片&#xff0c;支持高清CMOS Sensor输入&#xff0c;经ISP处理、视频前处理以及音视频编…...

Linux(11)——基础IO(上)

目录 一、理解文件 二、回顾C文件的接口 &#x1f4c4; C语言文件操作函数表 ​编辑&#x1f4c4; 三个文件流 三、系统文件I/O 1️⃣open 2️⃣close 3️⃣write 4️⃣read 四、文件描述符 &#x1f4a1;用户操作文件的底层逻辑是什么&#xff1f; &#x1f4…...

ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface

ABP-Book Store Application中文讲解 - Part 9: Authors: User Interface TBD 1. 汇总 ABP-Book Store Application中文讲解-汇总-CSDN博客 2. 前一章 ABP-Book Store Application中文讲解 - Part 8: Authors: Application Layer-CSDN博客 项目之间的引用关系。 ​​ 目…...

鸿蒙开发修改版本几个步骤

鸿蒙开发修改版本几个步骤 比如&#xff1a;5.0.4&#xff08;16&#xff09;版本改为5.0.2&#xff08;14&#xff09;版本 一、项目下的build-profile.json5 "products": [{"name": "default","signingConfig": "default&qu…...

Hive自定义函数案例(UDF、UDAF、UDTF)

目录 前提条件 背景 概念及适用场景 UDF&#xff08;User-Defined Function&#xff09; 概念 适用场景 UDAF&#xff08;User-Defined Aggregate Function&#xff09; 概念 适用场景 UDTF&#xff08;User-Defined Table-Generating Function&#xff09; 概念 适…...

【学习笔记】Circuit Tracing: Revealing Computational Graphs in Language Models

Circuit Tracing: Revealing Computational Graphs in Language Models 替代模型(Replacement Model)&#xff1a;用更多的可解释的特征来替代transformer模型的神经元。 归因图(Attribution Graph)&#xff1a;展示特征之间的相互影响&#xff0c;能够追踪模型生成输出时所采用…...

3D视觉重构工业智造:解码迁移科技如何用“硬核之眼“重塑生产节拍

一、工业视觉的进化论&#xff1a;从CCD到3D相机的范式革命 在汽车冲压车间里&#xff0c;传统CCD相机正面临四大检测困局&#xff1a; 平面感知局限&#xff1a;二维视觉无法捕捉曲面工件形变环境适应性差&#xff1a;反光板件导致误检率超12%动态捕捉延迟&#xff1a;传送带…...

Elasticsearch中的刷新(Refresh)和刷新间隔介绍

在 Elasticsearch 中,刷新(Refresh) 是控制索引数据何时对搜索可见的机制,而 刷新间隔(Refresh Interval) 则是配置该机制执行频率的参数。理解这两个概念对于平衡搜索实时性与写入性能至关重要。 一、刷新(Refresh)的本质 Lucene 索引结构与搜索可见性Elasticsearch …...

STM32标准库-TIM定时器

文章目录 一、TIM定时器1.1定时器1.2定时器类型1.1.1 高级定时器1.1.2通用定时器1.1.3基本定时器 二、定时中断基本结构预分频器时器计时器时序计数器无预装时序计数器有预装时序RCC时钟树 三、定时器定时中断3.1 接线图3.2代码3.3效果&#xff1a; 四、定时器外部中断4.1接线图…...

【算法训练营Day05】哈希表part1

文章目录 哈希表理论基础有效的字母异位词两个数组的交集快乐数两数之和 哈希表理论基础 几个值得关注的知识点&#xff1a; hash表用于快速的判断元素是否存在&#xff08;空间换时间&#xff09;其原理就是将数据通过散列函数映射到bucket中&#xff0c;如果发生hash碰撞&a…...