智能睡眠监测应用
一、睡眠监测系统的数据建模与状态定义
该应用通过四层数据结构构建睡眠监测的数字基础:
- 睡眠阶段枚举(
SleepStageType)
定义清醒(AWAKE)、浅度(LIGHT)、深度(DEEP)三种核心状态,为睡眠周期分析建立分类标准:
enum SleepStageType {
AWAKE = 'awake',
LIGHT = 'light',
DEEP = 'deep'
}
2. 阶段配置接口( SleepSTyeS )
存储各阶段的可视化属性与时间参数,如图标路径、持续时间范围及转换概率:
interface SleepSTyeS {
icon: string; // 阶段图标
title: string; // 阶段名称
color: string; // 对应颜色
minDuration: number; // 最小持续时间(秒)
maxDuration: number; // 最大持续时间(秒)
transitionChance: number; // 切换概率
}
3. 阶段数据接口( SleepStage )
记录单次阶段的完整信息,包括开始/结束时间、时长及占比数据:
interface SleepStage {
type: SleepStageType; // 阶段类型
icon: string; // 图标路径
title: string; // 阶段标题
duration: string; // 持续时长
color: string; // 阶段颜色
startTime: Date; // 开始时间
endTime: Date; // 结束时间
percentage: string; // 时长占比
}
4. 睡眠记录接口( SleepRecord )
聚合完整睡眠周期数据,形成可追溯的历史记录:
interface SleepRecord {
id: string; // 记录ID
startTime: Date; // 开始时间
endTime: Date; // 结束时间
duration: string; // 总时长
stages: SleepStage[]; // 阶段列表
qualityScore: number; // 质量评分(0-100)
}
这种分层建模方式使数据既能满足实时展示需求(如当前阶段),又能支持深度分析(如历史周期趋势),为算法评估提供结构化支撑。
二、睡眠周期的动态模拟与状态机实现
应用通过状态机模式驱动睡眠阶段的自动切换,核心逻辑集中在三个关键方法:
- 计时器驱动的状态更新
通过setInterval实现1秒精度的时间追踪,同时触发阶段检查:
startSleep() {
this.timerId = setInterval(() => {
this.sleepDuration = this.calculateDuration(this.startTime, new Date());
this.currentStageDuration++;
this.checkStageTransition(); // 检查阶段切换
}, 1000);
}
2. 阶段切换策略算法
结合时间阈值与随机概率实现自然的周期波动:
checkStageTransition() {
const config = this.stageConfig[this.currentStage];
if (this.currentStageDuration >= config.minDuration) {
if (this.currentStageDuration >= config.maxDuration) {
this.transitionToNextStage(); // 强制切换
} else if (Math.random() < config.transitionChance) {
this.transitionToNextStage(); // 概率切换
}
}
}
-
- 达到最小持续时间后,按配置概率触发切换(如浅度睡眠以40%概率进入深度睡眠)
- 超过最大持续时间则强制切换,模拟真实睡眠周期规律
- 状态流转控制
通过定义阶段转换规则,模拟睡眠周期的自然波动:
transitionToNextStage() {
const now = new Date();
this.saveCurrentStage(now); // 保存当前阶段
// 按规则计算下一阶段(如清醒阶段80%概率进入浅度睡眠)
switch (this.currentStage) {
case SleepStageType.AWAKE:
this.currentStage = Math.random() < 0.8 ? SleepStageType.LIGHT : SleepStageType.DEEP;
break;
// 其余阶段转换逻辑...
}
}
这种动态模拟机制无需真实传感器数据,即可通过算法还原睡眠周期的自然波动,为用户提供接近真实场景的阶段分析。
三、睡眠质量评估的多维度算法设计
应用通过三层指标体系构建睡眠质量评分(0-100分),算法实现兼顾专业性与可解释性:
- 深度睡眠占比分析
以深度睡眠占比20%-30%为基准,每超出1%加1分,不足1%减1分:
const deepStage = this.sleepStages.find(s => s.type === SleepStageType.DEEP);
if (deepStage) {
const deepPercentage = (deepSeconds / totalSeconds) * 100;
score += deepPercentage > 30
? Math.min(20, Math.round(deepPercentage - 30))
: deepPercentage < 20
? -Math.min(20, Math.round(20 - deepPercentage))
: 0;
}
2. 清醒状态惩罚机制
清醒占比超过10%后,每超出1%减2分,量化碎片化睡眠的影响:
const awakeStage = this.sleepStages.find(s => s.type === SleepStageType.AWAKE);
if (awakeStage) {
const awakePercentage = (awakeSeconds / totalSeconds) * 100;
score -= awakePercentage > 10
? Math.min(30, Math.round((awakePercentage - 10) * 2))
: 0;
}
3. 周期完整性评估
每完成3个阶段(约一个完整睡眠周期)加2分,最多加10分:
const cycleCount = Math.floor(this.sleepStages.length / 3);
score += Math.min(10, cycleCount * 2);
最终评分会转换为自然语言描述(如"良好:睡眠质量不错"),降低非专业用户的理解门槛。
四、交互界面的分层设计与数据可视化
应用采用"状态卡片+阶段分析"的双层UI架构,通过ArkTS声明式UI实现动态渲染:
- 睡眠状态主卡片
Stack() {
Circle().fill(this.isSleeping ? '#3498db' : '#4682b4')
.width(200).height(200).opacity(0.9);
Column({ space: 8 }) {
Text(this.sleepDuration).fontSize(32).fontWeight(FontWeight.Bold);
if (this.isSleeping) {
Text(`当前: ${this.stageConfig[this.currentStage].title}`)
.backgroundColor(this.stageConfig[this.currentStage].color + '50')
.padding(5).borderRadius(5);
}
}
}
-
- 圆形进度条背景(蓝色表示睡眠中,深蓝色表示已结束)
- 实时显示睡眠时长、阶段状态及时间信息
- 状态切换按钮通过颜色区分操作(绿色"开始"、红色"起床")
- 阶段分析面板
ForEach(this.sleepStages, (stage: SleepStage) => {
Column({ space: 8 }) {
Image(stage.icon).width(60).height(60)
.backgroundColor(stage.color + '10').borderRadius(10);
Text(stage.duration).fontSize(14).fontColor('#666666');
Text(stage.percentage).fontSize(12)
.backgroundColor(stage.color + '10').padding(5);
}
});
-
- 横向排列各阶段卡片,包含图标、时长及占比数据
- 评分卡片采用"数字+描述"双行显示(如"75/100 良好:睡眠质量不错")
- 空状态提示优化无数据场景的用户体验
这种设计遵循"数据可视化金字塔"原则——顶层展示核心指标(时长/评分),中层呈现阶段分布,底层隐藏时间戳等辅助数据,使信息层级清晰可辨。
五、附:代码
import promptAction from '@ohos.promptAction';
// 睡眠阶段类型
enum SleepStageType {
AWAKE = 'awake',
LIGHT = 'light',
DEEP = 'deep'
}
// 睡眠阶段数据接口
interface SleepStage {
type: SleepStageType; // 睡眠阶段类型
icon: string; // 图标资源路径
title: string; // 睡眠阶段标题
duration: string; // 该阶段持续时长
color: string; // 阶段对应颜色
startTime: Date; // 阶段开始时间
endTime: Date; // 阶段结束时间
percentage: string; // 占总睡眠时长百分比
}
// 睡眠记录接口
interface SleepRecord {
id: string;
startTime: Date;
endTime: Date;
duration: string;
stages: SleepStage[];
qualityScore: number; // 睡眠质量评分 (0-100)
}
interface SleepSTye {
SleepStageType:SleepStageType
SleepSTyeS:SleepSTyeS
}
interface SleepSTyeS {
icon: string;
title: string;
color: string;
minDuration: number;
maxDuration: number;
transitionChance: number;
}
@Entry
@Component
export struct Index {
@State sleepDuration: string = "0时0分0秒"; // 总睡眠时长
@State startTime: Date = new Date(); // 开始时间
@State endTimeStr: string = "0:0:0"; // 结束时间字符串
@State isSleeping: boolean = false; // 是否正在睡眠
@State timerId: number = -1; // 计时器ID
@State currentStage: SleepStageType = SleepStageType.LIGHT; // 当前睡眠阶段
@State currentStageStartTime: Date = new Date(); // 当前阶段开始时间
@State currentStageDuration: number = 0; // 当前阶段持续时间(秒)
@State sleepStages: SleepStage[] = []; // 所有睡眠阶段记录
@State sleepRecords: SleepRecord[] = []; // 历史睡眠记录
// 睡眠阶段配置
private stageConfig: Record<SleepStageType, SleepSTyeS> = {
[SleepStageType.AWAKE]: {
icon: "$r('app.media.awake_sleep_icon')",
title: "清醒睡眠",
color: "#e74c3c",
minDuration: 30,
maxDuration: 180,
transitionChance: 0.7
},
[SleepStageType.LIGHT]: {
icon: "$r('app.media.light_sleep_icon')",
title: "浅度睡眠",
color: "#2ecc71",
minDuration: 120,
maxDuration: 600,
transitionChance: 0.4
},
[SleepStageType.DEEP]: {
icon: "$r('app.media.deep_sleep_icon')",
title: "深度睡眠",
color: "#3498db",
minDuration: 300,
maxDuration: 1200,
transitionChance: 0.2
}
}
// 开始睡觉
startSleep() {
this.isSleeping = true;
this.startTime = new Date();
this.endTimeStr = "睡眠中...";
this.sleepDuration = "0时0分0秒";
this.currentStage = SleepStageType.LIGHT; // 初始为浅度睡眠
this.currentStageStartTime = new Date();
this.currentStageDuration = 0;
this.sleepStages = [];
// 启动计时器
this.timerId = setInterval(() => {
const now = new Date();
this.sleepDuration = this.calculateDuration(this.startTime, now);
this.currentStageDuration++;
// 检查是否需要切换睡眠阶段
this.checkStageTransition();
}, 1000);
promptAction.showToast({ message: "开始记录睡眠" });
}
// 结束睡觉
endSleep() {
clearInterval(this.timerId);
this.isSleeping = false;
const endTime = new Date();
this.endTimeStr = this.formatTime(endTime);
this.sleepDuration = this.calculateDuration(this.startTime, endTime);
// 保存最后一个睡眠阶段
this.saveCurrentStage(endTime);
// 计算各阶段百分比
this.calculateStagePercentages();
// 计算睡眠质量评分
const qualityScore = this.calculateQualityScore();
// 保存睡眠记录
const newRecord: SleepRecord = {
id: Date.now().toString(),
startTime: this.startTime,
endTime: endTime,
duration: this.sleepDuration,
stages: this.sleepStages,
qualityScore: qualityScore
};
this.sleepRecords = [newRecord, ...this.sleepRecords];
promptAction.showToast({
message: `睡眠记录已保存,质量评分: ${qualityScore}`
});
}
// 检查是否需要切换睡眠阶段
checkStageTransition() {
const config = this.stageConfig[this.currentStage];
// 如果达到最小持续时间,随机决定是否切换
if (this.currentStageDuration >= config.minDuration) {
// 达到最大持续时间,强制切换
if (this.currentStageDuration >= config.maxDuration) {
this.transitionToNextStage();
return;
}
// 随机概率切换
if (Math.random() < config.transitionChance) {
this.transitionToNextStage();
}
}
}
// 切换到下一睡眠阶段
transitionToNextStage() {
const now = new Date();
// 保存当前阶段
this.saveCurrentStage(now);
// 决定下一阶段
let nextStage: SleepStageType;
switch (this.currentStage) {
case SleepStageType.AWAKE:
nextStage = Math.random() < 0.8 ? SleepStageType.LIGHT : SleepStageType.DEEP;
break;
case SleepStageType.LIGHT:
nextStage = Math.random() < 0.6 ? SleepStageType.DEEP : SleepStageType.AWAKE;
break;
case SleepStageType.DEEP:
nextStage = Math.random() < 0.8 ? SleepStageType.LIGHT : SleepStageType.AWAKE;
break;
}
// 更新当前阶段
this.currentStage = nextStage;
this.currentStageStartTime = now;
this.currentStageDuration = 0;
}
// 保存当前睡眠阶段
saveCurrentStage(endTime: Date) {
const config = this.stageConfig[this.currentStage];
const duration = this.currentStageDuration;
// 计算小时和分钟
const hours = Math.floor(duration / 3600);
const minutes = Math.floor((duration % 3600) / 60);
const seconds = duration % 60;
// 格式化持续时间
let durationStr = '';
if (hours > 0) durationStr += `${hours}小时`;
if (minutes > 0) durationStr += `${minutes}分`;
if (seconds > 0 || durationStr === '') durationStr += `${seconds}秒`;
// 添加到睡眠阶段列表
this.sleepStages.push({
type: this.currentStage,
icon: config.icon,
title: config.title,
duration: durationStr,
color: config.color,
startTime: new Date(this.currentStageStartTime),
endTime: new Date(endTime),
percentage: "0%"
});
}
// 计算各阶段百分比
calculateStagePercentages() {
const totalSeconds = this.getTotalSeconds(this.sleepDuration);
if (totalSeconds === 0) return;
for (const stage of this.sleepStages) {
const stageSeconds = (stage.endTime.getTime() - stage.startTime.getTime()) / 1000;
const percentage = Math.round((stageSeconds / totalSeconds) * 100);
stage.percentage = `${percentage}%`;
}
}
// 计算睡眠质量评分
calculateQualityScore(): number {
let score = 50; // 基础分
// 深度睡眠占比越高,分数越高
const deepStage = this.sleepStages.find(s => s.type === SleepStageType.DEEP);
if (deepStage) {
const deepSeconds = (deepStage.endTime.getTime() - deepStage.startTime.getTime()) / 1000;
const totalSeconds = this.getTotalSeconds(this.sleepDuration);
const deepPercentage = (deepSeconds / totalSeconds) * 100;
// 深度睡眠占比20%-30%为正常,每超出1%加1分,不足1%减1分
if (deepPercentage > 30) {
score += Math.min(20, Math.round(deepPercentage - 30));
} else if (deepPercentage < 20) {
score -= Math.min(20, Math.round(20 - deepPercentage));
}
}
// 清醒阶段越少,分数越高
const awakeStage = this.sleepStages.find(s => s.type === SleepStageType.AWAKE);
if (awakeStage) {
const awakeSeconds = (awakeStage.endTime.getTime() - awakeStage.startTime.getTime()) / 1000;
const totalSeconds = this.getTotalSeconds(this.sleepDuration);
const awakePercentage = (awakeSeconds / totalSeconds) * 100;
// 清醒占比10%以下为正常,每超出1%减2分
if (awakePercentage > 10) {
score -= Math.min(30, Math.round((awakePercentage - 10) * 2));
}
}
// 睡眠周期越多,分数越高
const cycleCount = Math.floor(this.sleepStages.length / 3); // 每3个阶段算一个周期
score += Math.min(10, cycleCount * 2);
// 确保分数在0-100范围内
return Math.max(0, Math.min(100, score));
}
// 计算时间差
calculateDuration(start: Date, end: Date): string {
const diffMs = end.getTime() - start.getTime();
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diffMs % (1000 * 60)) / 1000);
return `${hours}时${minutes}分${seconds}秒`;
}
// 格式化时间
formatTime(date: Date): string {
return `${date.getHours().toString().padStart(2, '0')}:${
date.getMinutes().toString().padStart(2, '0')}:${
date.getSeconds().toString().padStart(2, '0')
}`;
}
// 获取总秒数
getTotalSeconds(duration: string): number {
const parts = duration.split('时');
const hourPart = parts[0];
const minutePart = parts[1].split('分')[0];
const secondPart = parts[1].split('分')[1].split('秒')[0];
const hours = Number(hourPart);
const minutes = Number(minutePart);
const seconds = Number(secondPart || 0);
return hours * 3600 + minutes * 60 + seconds;
}
build() {
Column({ space: 15 }) {
// 睡眠状态卡片
Column({ space: 20 }) {
Stack() {
// 圆形进度条
Circle()
.fill(this.isSleeping ? '#3498db' : '#4682b4')
.width(200)
.height(200)
.opacity(0.9);
Column({ space: 8 }) {
Text(this.sleepDuration)
.fontSize(32)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold);
Text(`开始时间: ${this.formatTime(this.startTime)}`)
.fontSize(14)
.fontColor('#FFFFFF');
Text(`结束时间: ${this.endTimeStr}`)
.fontSize(14)
.fontColor('#FFFFFF');
if (this.isSleeping) {
Text(`当前: ${this.stageConfig[this.currentStage].title}`)
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor(this.stageConfig[this.currentStage].color + '50')
.padding(5)
.borderRadius(5);
}
}
}
.margin({ top: 20 });
// 睡眠控制按钮
Button(this.isSleeping ? '起床' : '开始睡觉')
.width(150)
.height(50)
.backgroundColor('#FFFFFF')
.fontColor(this.isSleeping ? '#e74c3c' : '#3498db')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.isSleeping ? this.endSleep() : this.startSleep();
});
}
.width('100%')
.height(this.isSleeping ? 350 : 320)
.backgroundColor(this.isSleeping ? '#3498db' : '#4682b4')
.borderRadius(20)
.shadow({ radius: 5, color: '#0000001A' });
// 睡眠阶段展示
Text('睡眠阶段分析')
.fontSize(18)
.fontColor('#333')
.fontWeight(FontWeight.Bold)
.margin({ top: 10, bottom: 15 });
// 睡眠阶段统计
if (!this.isSleeping && this.sleepStages.length > 0) {
Row({ space: 15 }) {
ForEach(this.sleepStages, (stage: SleepStage) => {
Column({ space: 8 }) {
Image(stage.icon)
.width(60)
.height(60)
.backgroundColor(stage.color + '10')
.borderRadius(10);
Text(stage.title)
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium);
Text(stage.duration)
.fontSize(14)
.fontColor('#666666');
Text(stage.percentage)
.fontSize(12)
.fontColor(stage.color)
.backgroundColor(stage.color + '10')
.padding(5)
.borderRadius(10);
}
.flexGrow(1)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center);
});
}
.width('100%')
.padding({ left: 15, right: 15 });
// 睡眠质量评分
if (this.sleepRecords.length > 0) {
Column() {
Text(`睡眠质量评分: ${this.sleepRecords[0].qualityScore}/100`)
.fontSize(16)
.fontColor('#333')
.fontWeight(FontWeight.Medium);
Text(this.getQualityDescription(this.sleepRecords[0].qualityScore))
.fontSize(14)
.fontColor('#666')
.margin({ top: 5 });
}
.width('100%')
.padding(15)
.backgroundColor('#f5f7fa')
.borderRadius(15)
.margin({ top: 15 });
}
} else {
Text(this.isSleeping ? '睡眠中,阶段数据将在结束后显示' : '暂无睡眠阶段数据')
.fontSize(14)
.fontColor('#666')
.width('100%')
.textAlign(TextAlign.Center)
.margin({ top: 15 });
}
}
.height('100%')
.padding(15);
}
// 根据睡眠质量评分获取描述
getQualityDescription(score: number): string {
if (score >= 90) return '优秀:睡眠质量极佳,身心充分恢复';
if (score >= 75) return '良好:睡眠质量不错,身体得到较好休息';
if (score >= 60) return '一般:睡眠质量一般,建议调整作息';
if (score >= 40) return '较差:睡眠质量不佳,可能影响日常状态';
return '很差:睡眠质量严重不足,建议就医检查';
}
}