HarmonyOS开发:湿度传感器与环境监测

举报
Jack20 发表于 2026/06/21 16:04:46 2026/06/21
【摘要】 HarmonyOS开发:湿度传感器与环境监测核心要点:本文全面讲解HarmonyOS湿度传感器(Humidity Sensor)的数据采集、环境舒适度评估模型、多参数环境监测系统搭建以及基于温湿度联动的智能场景控制方案,涵盖从传感器底层原理到上层应用架构的完整技术链路。项目说明HarmonyOS版本5.0+(API 12+)开发语言ArkTS核心API@ohos.sensor (humid...

HarmonyOS开发:湿度传感器与环境监测

核心要点:本文全面讲解HarmonyOS湿度传感器(Humidity Sensor)的数据采集、环境舒适度评估模型、多参数环境监测系统搭建以及基于温湿度联动的智能场景控制方案,涵盖从传感器底层原理到上层应用架构的完整技术链路。

项目 说明
开发语言 ArkTS
核心API @ohos.sensor (humidity, ambient_temperature)
权限声明 ohos.permission.ACCELEROMETER

一、背景与动机

1.1 为什么需要湿度传感器

湿度是影响人类生活质量和健康的重要环境参数。在物联网和智能家居快速发展的今天,湿度感知能力已经从专业气象领域走向消费级应用:

  • 健康舒适度管理:人体最适宜的相对湿度为40%~60%,湿度过高或过低都会引发健康问题
  • 智能家居联动:根据湿度自动控制加湿器、除湿机、空调等设备
  • 工业环境监控:电子制造、食品存储、药品仓储等场景对湿度有严格要求
  • 农业精准种植:大棚温湿度监测与自动灌溉联动
  • 气象数据采集:便携式气象站、户外运动环境评估

1.2 湿度基础知识

绝对湿度(Absolute Humidity):单位体积空气中所含水蒸气的质量,单位g/m³。

相对湿度(Relative Humidity, RH):当前水蒸气分压与同温度下饱和水蒸气压的比值,以百分比表示:

RH = (P_water / P_sat) × 100%

其中:

  • P_water:当前水蒸气分压
  • P_sat:同温度下饱和水蒸气压(仅与温度有关)

露点温度(Dew Point):在当前水蒸气含量不变的情况下,使空气达到饱和所需的降温温度。露点温度越高,人体感觉越闷热。

1.3 HarmonyOS环境监测架构

flowchart TB
    subgraph Sensors["传感器层"]
        S1["湿度传感器<br/>Relative Humidity"]
        S2["温度传感器<br/>Ambient Temperature"]
        S3["气压传感器<br/>Barometer"]
        S4["环境光传感器<br/>Ambient Light"]
    end

    subgraph Collect["数据采集层"]
        C1["传感器订阅管理"]
        C2["数据校准与滤波"]
        C3["时间对齐与同步"]
    end

    subgraph Process["数据处理层"]
        P1["舒适度指数计算<br/>THI / PMV"]
        P2["露点温度推算"]
        P3["环境等级判定"]
        P4["趋势预测"]
    end

    subgraph App["应用层"]
        A1["环境监测仪表盘"]
        A2["智能场景联动"]
        A3["健康提醒服务"]
        A4["数据上报与云同步"]
    end

    S1 --> C1
    S2 --> C1
    S3 --> C1
    S4 --> C1
    C1 --> C2 --> C3
    C3 --> P1
    C3 --> P2
    C3 --> P3
    C3 --> P4
    P1 --> A1
    P2 --> A2
    P3 --> A3
    P4 --> A4

    classDef sensorStyle fill:#1a1a2e,stroke:#e94560,color:#eee,stroke-width:2px
    classDef collectStyle fill:#16213e,stroke:#0f3460,color:#eee,stroke-width:2px
    classDef processStyle fill:#0f3460,stroke:#533483,color:#eee,stroke-width:2px
    classDef appStyle fill:#533483,stroke:#e94560,color:#eee,stroke-width:2px

    class S1,S2,S3,S4 sensorStyle
    class C1,C2,C3 collectStyle
    class P1,P2,P3,P4 processStyle
    class A1,A2,A3,A4 appStyle

二、核心原理

2.1 湿度传感器工作原理

消费级设备中常用的湿度传感器主要有两种类型:

电容式湿度传感器(Capacitive)

  • 原理:感湿介质(通常为高分子聚合物)吸收水分子后介电常数发生变化,导致电容值改变
  • 特点:响应快(< 5秒)、功耗低、长期稳定性好
  • 精度:±2% RH(典型值)
  • 代表芯片:Bosch BME280、Sensirion SHT30

电阻式湿度传感器(Resistive)

  • 原理:感湿材料的电阻值随湿度变化
  • 特点:结构简单、成本低
  • 精度:±3% ~ ±5% RH
  • 缺点:存在迟滞效应、长期漂移较大

2.2 温湿度关联计算

湿度的感知和计算离不开温度参数。以下是几个关键的关联计算:

饱和水蒸气压计算(Magnus公式)

P_sat(T) = 6.1078 × exp(17.27 × T / (T + 237.3))

其中T为温度(°C),P_sat单位为hPa。

露点温度计算

T_dew = (237.3 × ln(RH/100) + 237.3 × (17.27 × T / (T + 237.3))) /
        (17.27 - ln(RH/100) - 17.27 × T / (T + 237.3))

绝对湿度计算

AH = (216.7 × RH/100 × P_sat(T)) / (273.15 + T)

单位:g/m³

2.3 人体舒适度评估模型

flowchart LR
    subgraph Input["环境参数输入"]
        I1["温度 T"]
        I2["湿度 RH"]
        I3["风速 v(可选)"]
        I4["辐射温度 Tr(可选)"]
    end

    subgraph Models["舒适度模型"]
        M1["THI 温湿指数<br/>简易模型"]
        M2["PMV 预测平均投票<br/>ISO 7730标准"]
        M3["Heat Index<br/>体感温度"]
    end

    subgraph Output["评估结果"]
        O1["舒适度等级<br/>冷/凉/舒适/暖/热"]
        O2["体感温度"]
        O3["健康建议"]
    end

    I1 --> M1
    I2 --> M1
    I1 --> M2
    I2 --> M2
    I3 --> M2
    I4 --> M2
    I1 --> M3
    I2 --> M3
    M1 --> O1
    M2 --> O1
    M2 --> O2
    M1 --> O3
    M3 --> O3

    classDef inputStyle fill:#1a1a2e,stroke:#e94560,color:#eee,stroke-width:2px
    classDef modelStyle fill:#0f3460,stroke:#533483,color:#eee,stroke-width:2px
    classDef outputStyle fill:#533483,stroke:#e94560,color:#eee,stroke-width:2px

    class I1,I2,I3,I4 inputStyle
    class M1,M2,M3 modelStyle
    class O1,O2,O3 outputStyle

THI(Temperature-Humidity Index)温湿指数是最常用的简易舒适度评估模型:

THI = T - (0.55 - 0.0055 × RH) × (T - 14.5)
THI范围 舒适度等级 人体感受
< 15 寒冷 需要取暖
15 ~ 20 凉爽 较为舒适
20 ~ 25 舒适 最佳状态
25 ~ 28 温暖 略感闷热
28 ~ 30 闷热 需要降温
> 30 酷热 不宜户外活动

三、代码实战

3.1 湿度传感器数据订阅与处理

// HumiditySensor.ets - 湿度传感器数据订阅与处理
import sensor from '@ohos.sensor';
import { BusinessError } from '@ohos.base';

// 湿度传感器数据接口
interface HumiditySensorData {
  humidity: number;    // 相对湿度(%)
  timestamp: number;   // 时间戳(纳秒)
}

// 温度传感器数据接口
interface TemperatureSensorData {
  temperature: number; // 温度(°C)
  timestamp: number;   // 时间戳(纳秒)
}

// 环境数据快照
interface EnvironmentSnapshot {
  humidity: number;       // 相对湿度
  temperature: number;    // 温度
  dewPoint: number;       // 露点温度
  absoluteHumidity: number; // 绝对湿度
  thi: number;            // 温湿指数
  comfortLevel: string;   // 舒适度等级
  timestamp: number;      // 时间戳
}

@Entry
@Component
struct HumiditySensorPage {
  // 当前湿度
  @State currentHumidity: number = 50;
  // 当前温度
  @State currentTemperature: number = 25;
  // 露点温度
  @State dewPoint: number = 0;
  // 绝对湿度
  @State absoluteHumidity: number = 0;
  // 温湿指数
  @State thi: number = 0;
  // 舒适度等级
  @State comfortLevel: string = '舒适';
  // 舒适度颜色
  @State comfortColor: string = '#4caf50';
  // 环境建议
  @State environmentAdvice: string = '';
  // 历史数据
  @State historyData: EnvironmentSnapshot[] = [];
  // 监控状态
  @State isMonitoring: boolean = false;
  // 错误信息
  @State errorMessage: string = '';

  // 传感器订阅ID
  private humiditySubscriberId: number = -1;
  private temperatureSubscriberId: number = -1;

  aboutToAppear() {
    this.startMonitoring();
  }

  aboutToDisappear() {
    this.stopMonitoring();
  }

  /**
   * 启动环境监测
   */
  private startMonitoring(): void {
    try {
      // 订阅湿度传感器
      if (sensor.isSensorAvailable(sensor.SensorType.HUMIDITY)) {
        this.humiditySubscriberId = sensor.on(
          sensor.SensorType.HUMIDITY,
          (data: HumiditySensorData) => {
            this.currentHumidity = Math.round(data.humidity * 10) / 10;
            this.updateEnvironmentData();
          },
          { interval: 1000000000 } // 1秒采样
        );
      } else {
        this.errorMessage = '湿度传感器不可用';
      }

      // 订阅温度传感器
      if (sensor.isSensorAvailable(sensor.SensorType.AMBIENT_TEMPERATURE)) {
        this.temperatureSubscriberId = sensor.on(
          sensor.SensorType.AMBIENT_TEMPERATURE,
          (data: TemperatureSensorData) => {
            this.currentTemperature = Math.round(data.temperature * 10) / 10;
            this.updateEnvironmentData();
          },
          { interval: 1000000000 }
        );
      }

      this.isMonitoring = true;
    } catch (error) {
      const err = error as BusinessError;
      this.errorMessage = `监测启动失败: ${err.message}`;
    }
  }

  /**
   * 停止环境监测
   */
  private stopMonitoring(): void {
    if (this.humiditySubscriberId !== -1) {
      try { sensor.off(sensor.SensorType.HUMIDITY, this.humiditySubscriberId); } catch (e) {}
    }
    if (this.temperatureSubscriberId !== -1) {
      try { sensor.off(sensor.SensorType.AMBIENT_TEMPERATURE, this.temperatureSubscriberId); } catch (e) {}
    }
    this.isMonitoring = false;
  }

  /**
   * 更新环境数据
   * 当温度或湿度变化时重新计算所有衍生参数
   */
  private updateEnvironmentData(): void {
    const T = this.currentTemperature;
    const RH = this.currentHumidity;

    // 计算露点温度
    this.dewPoint = this.calculateDewPoint(T, RH);

    // 计算绝对湿度
    this.absoluteHumidity = this.calculateAbsoluteHumidity(T, RH);

    // 计算温湿指数
    this.thi = this.calculateTHI(T, RH);

    // 评估舒适度
    const comfort = this.evaluateComfort(this.thi);
    this.comfortLevel = comfort.level;
    this.comfortColor = comfort.color;
    this.environmentAdvice = comfort.advice;

    // 记录历史
    const snapshot: EnvironmentSnapshot = {
      humidity: RH,
      temperature: T,
      dewPoint: this.dewPoint,
      absoluteHumidity: this.absoluteHumidity,
      thi: this.thi,
      comfortLevel: this.comfortLevel,
      timestamp: Date.now()
    };
    this.historyData.push(snapshot);
    if (this.historyData.length > 200) {
      this.historyData.shift();
    }
  }

  /**
   * 计算露点温度(Magnus公式)
   * @param T 温度(°C)
   * @param RH 相对湿度(%)
   * @returns 露点温度(°C)
   */
  private calculateDewPoint(T: number, RH: number): number {
    const a = 17.27;
    const b = 237.3;
    const alpha = (a * T) / (b + T) + Math.log(RH / 100);
    const dewPoint = (b * alpha) / (a - alpha);
    return Math.round(dewPoint * 10) / 10;
  }

  /**
   * 计算绝对湿度
   * @param T 温度(°C)
   * @param RH 相对湿度(%)
   * @returns 绝对湿度(g/m³)
   */
  private calculateAbsoluteHumidity(T: number, RH: number): number {
    // 饱和水蒸气压(Magnus公式)
    const pSat = 6.1078 * Math.exp((17.27 * T) / (T + 237.3));
    // 绝对湿度
    const ah = (216.7 * (RH / 100) * pSat) / (273.15 + T);
    return Math.round(ah * 100) / 100;
  }

  /**
   * 计算温湿指数(THI)
   * @param T 温度(°C)
   * @param RH 相对湿度(%)
   * @returns 温湿指数
   */
  private calculateTHI(T: number, RH: number): number {
    const thi = T - (0.55 - 0.0055 * RH) * (T - 14.5);
    return Math.round(thi * 10) / 10;
  }

  /**
   * 评估舒适度
   */
  private evaluateComfort(thi: number): { level: string; color: string; advice: string } {
    if (thi < 15) {
      return { level: '寒冷', color: '#2196f3', advice: '环境偏冷,建议增加衣物或开启暖气' };
    }
    if (thi < 20) {
      return { level: '凉爽', color: '#4caf50', advice: '环境凉爽,体感舒适' };
    }
    if (thi < 25) {
      return { level: '舒适', color: '#4caf50', advice: '环境舒适,适宜工作和休息' };
    }
    if (thi < 28) {
      return { level: '温暖', color: '#ff9800', advice: '略感闷热,建议开窗通风' };
    }
    if (thi < 30) {
      return { level: '闷热', color: '#ff5722', advice: '环境闷热,建议开启空调或风扇' };
    }
    return { level: '酷热', color: '#f44336', advice: '⚠️ 环境酷热,注意防暑降温,避免户外活动' };
  }

  build() {
    Scroll() {
      Column() {
        // 标题
        Text('湿度传感器与环境监测')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#e0e0e0')
          .margin({ bottom: 24 })

        // 舒适度等级卡片
        Column() {
          Text('环境舒适度')
            .fontSize(14)
            .fontColor('#888888')
          Text(this.comfortLevel)
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
            .fontColor(this.comfortColor)
            .margin({ top: 8 })
          Text(`THI: ${this.thi}`)
            .fontSize(14)
            .fontColor('#888888')
            .margin({ top: 4 })
        }
        .width('90%')
        .padding(24)
        .borderRadius(16)
        .backgroundColor('#1a1a2e')
        .margin({ bottom: 16 })

        // 温湿度双参数显示
        Row() {
          // 温度
          Column() {
            Text('温度')
              .fontSize(12)
              .fontColor('#888888')
            Text(`${this.currentTemperature}`)
              .fontSize(32)
              .fontWeight(FontWeight.Bold)
              .fontColor('#e94560')
            Text('°C')
              .fontSize(14)
              .fontColor('#888888')
          }
          .layoutWeight(1)

          // 分隔线
          Divider()
            .vertical(true)
            .height(60)
            .color('#333333')

          // 湿度
          Column() {
            Text('湿度')
              .fontSize(12)
              .fontColor('#888888')
            Text(`${this.currentHumidity}`)
              .fontSize(32)
              .fontWeight(FontWeight.Bold)
              .fontColor('#533483')
            Text('%RH')
              .fontSize(14)
              .fontColor('#888888')
          }
          .layoutWeight(1)
        }
        .width('90%')
        .padding(20)
        .borderRadius(16)
        .backgroundColor('#1a1a2e')
        .margin({ bottom: 16 })

        // 衍生参数
        Column() {
          Text('衍生参数')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#e0e0e0')
            .margin({ bottom: 12 })

          this.DerivedParamRow('露点温度', `${this.dewPoint} °C`)
          this.DerivedParamRow('绝对湿度', `${this.absoluteHumidity} g/m³`)
          this.DerivedParamRow('温湿指数', `${this.thi}`)
        }
        .width('90%')
        .padding(20)
        .borderRadius(16)
        .backgroundColor('#1a1a2e')
        .alignItems(HorizontalAlign.Start)
        .margin({ bottom: 16 })

        // 环境建议
        if (this.environmentAdvice) {
          Text(this.environmentAdvice)
            .fontSize(14)
            .fontColor('#e0e0e0')
            .width('90%')
            .padding(16)
            .borderRadius(12)
            .backgroundColor('#1a2a1e')
            .margin({ bottom: 16 })
        }

        // 错误信息
        if (this.errorMessage) {
          Text(this.errorMessage)
            .fontSize(12)
            .fontColor('#f44336')
            .margin({ bottom: 16 })
        }
      }
      .width('100%')
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0a0a1a')
  }

  @Builder
  DerivedParamRow(label: string, value: string) {
    Row() {
      Text(label)
        .fontSize(14)
        .fontColor('#888888')
        .layoutWeight(1)
      Text(value)
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor('#e0e0e0')
    }
    .width('100%')
    .padding({ top: 6, bottom: 6 })
  }
}

3.2 环境舒适度评估引擎

// ComfortEngine.ets - 环境舒适度评估引擎

/**
 * 舒适度评估结果
 */
export interface ComfortAssessment {
  // 综合评分(0-100,100为最舒适)
  score: number;
  // 舒适度等级
  level: ComfortLevel;
  // 温度评分
  tempScore: number;
  // 湿度评分
  humidityScore: number;
  // 体感温度
  apparentTemp: number;
  // 健康风险等级
  healthRisk: HealthRisk;
  // 改善建议
  suggestions: string[];
}

/**
 * 舒适度等级枚举
 */
export enum ComfortLevel {
  VERY_COLD = '极冷',
  COLD = '寒冷',
  COOL = '凉爽',
  COMFORTABLE = '舒适',
  WARM = '温暖',
  HOT = '闷热',
  VERY_HOT = '酷热'
}

/**
 * 健康风险等级
 */
export enum HealthRisk {
  NONE = '无风险',
  LOW = '低风险',
  MEDIUM = '中等风险',
  HIGH = '高风险',
  CRITICAL = '极高风险'
}

/**
 * 环境舒适度评估引擎
 * 综合温度、湿度等多个参数评估环境舒适度
 */
export class ComfortEngine {
  // 最适温度范围
  private readonly OPTIMAL_TEMP_MIN = 20;
  private readonly OPTIMAL_TEMP_MAX = 26;
  // 最适湿度范围
  private readonly OPTIMAL_HUMIDITY_MIN = 40;
  private readonly OPTIMAL_HUMIDITY_MAX = 60;

  /**
   * 综合评估环境舒适度
   * @param temperature 温度(°C)
   * @param humidity 相对湿度(%)
   * @param windSpeed 风速(m/s,可选,默认0.1)
   * @returns 舒适度评估结果
   */
  public assess(temperature: number, humidity: number, windSpeed: number = 0.1): ComfortAssessment {
    // 计算各项评分
    const tempScore = this.calculateTempScore(temperature);
    const humidityScore = this.calculateHumidityScore(humidity);

    // 综合评分(温度权重60%,湿度权重40%)
    const score = Math.round(tempScore * 0.6 + humidityScore * 0.4);

    // 计算体感温度(Heat Index)
    const apparentTemp = this.calculateApparentTemp(temperature, humidity, windSpeed);

    // 判定舒适度等级
    const level = this.determineComfortLevel(score);

    // 评估健康风险
    const healthRisk = this.assessHealthRisk(temperature, humidity);

    // 生成改善建议
    const suggestions = this.generateSuggestions(temperature, humidity, tempScore, humidityScore);

    return {
      score,
      level,
      tempScore,
      humidityScore,
      apparentTemp,
      healthRisk,
      suggestions
    };
  }

  /**
   * 计算温度评分(0-100)
   * 使用高斯函数,最适温度范围得满分
   */
  private calculateTempScore(temp: number): number {
    if (temp >= this.OPTIMAL_TEMP_MIN && temp <= this.OPTIMAL_TEMP_MAX) {
      return 100;
    }
    // 偏离最适范围,按高斯衰减
    const center = (this.OPTIMAL_TEMP_MIN + this.OPTIMAL_TEMP_MAX) / 2;
    const sigma = 8; // 标准差
    const score = 100 * Math.exp(-Math.pow(temp - center, 2) / (2 * sigma * sigma));
    return Math.max(0, Math.round(score));
  }

  /**
   * 计算湿度评分(0-100)
   */
  private calculateHumidityScore(humidity: number): number {
    if (humidity >= this.OPTIMAL_HUMIDITY_MIN && humidity <= this.OPTIMAL_HUMIDITY_MAX) {
      return 100;
    }
    const center = (this.OPTIMAL_HUMIDITY_MIN + this.OPTIMAL_HUMIDITY_MAX) / 2;
    const sigma = 20;
    const score = 100 * Math.exp(-Math.pow(humidity - center, 2) / (2 * sigma * sigma));
    return Math.max(0, Math.round(score));
  }

  /**
   * 计算体感温度
   * 使用Steadman公式(简化版)
   */
  private calculateApparentTemp(T: number, RH: number, V: number): number {
    // 风冷效应
    const windChill = T < 10 && V > 1.3 ?
      13.12 + 0.6215 * T - 11.37 * Math.pow(V, 0.16) + 0.3965 * T * Math.pow(V, 0.16) :
      T;

    // 高温高湿效应(Heat Index)
    if (T >= 27 && RH >= 40) {
      const HI = -8.785 + 1.611 * T + 2.339 * RH -
        0.1461 * T * RH - 0.01231 * T * T -
        0.01642 * RH * RH + 0.002212 * T * T * RH +
        0.0007255 * T * RH * RH - 0.000003582 * T * T * RH * RH;
      return Math.round(HI * 10) / 10;
    }

    return Math.round(windChill * 10) / 10;
  }

  /**
   * 判定舒适度等级
   */
  private determineComfortLevel(score: number): ComfortLevel {
    if (score >= 90) return ComfortLevel.COMFORTABLE;
    if (score >= 75) return ComfortLevel.COOL;
    if (score >= 60) return ComfortLevel.WARM;
    if (score >= 40) return ComfortLevel.COLD;
    if (score >= 25) return ComfortLevel.HOT;
    if (score >= 10) return ComfortLevel.VERY_COLD;
    return ComfortLevel.VERY_HOT;
  }

  /**
   * 评估健康风险
   */
  private assessHealthRisk(T: number, RH: number): HealthRisk {
    // 极端温度风险
    if (T > 40 || T < -10) return HealthRisk.CRITICAL;
    if (T > 35 || T < 0) return HealthRisk.HIGH;

    // 高湿风险(霉菌滋生)
    if (RH > 80) return HealthRisk.MEDIUM;
    if (RH > 70 && T > 25) return HealthRisk.MEDIUM;

    // 低湿风险(呼吸道干燥)
    if (RH < 20) return HealthRisk.MEDIUM;
    if (RH < 30) return HealthRisk.LOW;

    return HealthRisk.NONE;
  }

  /**
   * 生成改善建议
   */
  private generateSuggestions(T: number, RH: number, tempScore: number, humidityScore: number): string[] {
    const suggestions: string[] = [];

    if (tempScore < 80) {
      if (T < this.OPTIMAL_TEMP_MIN) {
        suggestions.push(`温度偏低(${T}°C),建议开启暖气或增加衣物`);
      } else {
        suggestions.push(`温度偏高(${T}°C),建议开启空调降温`);
      }
    }

    if (humidityScore < 80) {
      if (RH < this.OPTIMAL_HUMIDITY_MIN) {
        suggestions.push(`湿度偏低(${RH}%),建议使用加湿器,目标湿度40%-60%`);
      } else {
        suggestions.push(`湿度偏高(${RH}%),建议使用除湿机或开窗通风`);
      }
    }

    if (RH > 70 && T > 25) {
      suggestions.push('高温高湿环境,注意防暑,及时补充水分');
    }

    if (RH < 25) {
      suggestions.push('空气干燥,注意皮肤保湿和呼吸道保护');
    }

    if (suggestions.length === 0) {
      suggestions.push('当前环境舒适,无需特别调整');
    }

    return suggestions;
  }
}

3.3 智能环境监测仪表盘

// EnvironmentDashboard.ets - 智能环境监测仪表盘
import sensor from '@ohos.sensor';
import { BusinessError } from '@ohos.base';
import { ComfortEngine, ComfortAssessment, ComfortLevel, HealthRisk } from './ComfortEngine';

// 传感器数据接口
interface HumiditySensorData { humidity: number; timestamp: number; }
interface TemperatureSensorData { temperature: number; timestamp: number; }

@Entry
@Component
struct EnvironmentDashboardPage {
  // 环境参数
  @State temperature: number = 25;
  @State humidity: number = 50;
  // 舒适度评估
  @State assessment: ComfortAssessment | null = null;
  // 监控状态
  @State isMonitoring: boolean = false;
  // 历史舒适度评分
  @State scoreHistory: number[] = [];
  // 场景建议
  @State sceneRecommendation: string = '';

  // 引擎实例
  private comfortEngine: ComfortEngine = new ComfortEngine();
  // 传感器订阅
  private humiditySubId: number = -1;
  private tempSubId: number = -1;
  // 更新节流
  private lastUpdateTime: number = 0;

  aboutToAppear() {
    this.startMonitoring();
  }

  aboutToDisappear() {
    this.stopMonitoring();
  }

  private startMonitoring(): void {
    try {
      if (sensor.isSensorAvailable(sensor.SensorType.HUMIDITY)) {
        this.humiditySubId = sensor.on(
          sensor.SensorType.HUMIDITY,
          (data: HumiditySensorData) => {
            this.humidity = Math.round(data.humidity * 10) / 10;
            this.throttledUpdate();
          },
          { interval: 1000000000 }
        );
      }

      if (sensor.isSensorAvailable(sensor.SensorType.AMBIENT_TEMPERATURE)) {
        this.tempSubId = sensor.on(
          sensor.SensorType.AMBIENT_TEMPERATURE,
          (data: TemperatureSensorData) => {
            this.temperature = Math.round(data.temperature * 10) / 10;
            this.throttledUpdate();
          },
          { interval: 1000000000 }
        );
      }

      this.isMonitoring = true;
      this.updateAssessment();
    } catch (error) {
      const err = error as BusinessError;
      console.error(`监测启动失败: ${err.message}`);
    }
  }

  private stopMonitoring(): void {
    if (this.humiditySubId !== -1) {
      try { sensor.off(sensor.SensorType.HUMIDITY, this.humiditySubId); } catch (e) {}
    }
    if (this.tempSubId !== -1) {
      try { sensor.off(sensor.SensorType.AMBIENT_TEMPERATURE, this.tempSubId); } catch (e) {}
    }
    this.isMonitoring = false;
  }

  /**
   * 节流更新(最小间隔2秒)
   */
  private throttledUpdate(): void {
    const now = Date.now();
    if (now - this.lastUpdateTime < 2000) return;
    this.lastUpdateTime = now;
    this.updateAssessment();
  }

  /**
   * 更新舒适度评估
   */
  private updateAssessment(): void {
    this.assessment = this.comfortEngine.assess(this.temperature, this.humidity);

    // 记录评分历史
    this.scoreHistory.push(this.assessment.score);
    if (this.scoreHistory.length > 50) {
      this.scoreHistory.shift();
    }

    // 生成场景建议
    this.sceneRecommendation = this.getSceneRecommendation();
  }

  /**
   * 获取场景建议
   */
  private getSceneRecommendation(): string {
    if (!this.assessment) return '';
    const { score, tempScore, humidityScore } = this.assessment;

    if (score >= 85) return '🏠 当前环境极佳,适合居家办公和休息';
    if (tempScore < 60 && humidityScore < 60) return '🌡️ 温湿度均不佳,建议同时调节空调和加湿器';
    if (tempScore < 60) return '❄️ 温度不适宜,建议调节空调温度';
    if (humidityScore < 60) return '💧 湿度不适宜,建议使用加湿/除湿设备';
    return '✅ 环境尚可,可适当微调';
  }

  /**
   * 获取评分颜色
   */
  private getScoreColor(score: number): string {
    if (score >= 80) return '#4caf50';
    if (score >= 60) return '#ff9800';
    if (score >= 40) return '#ff5722';
    return '#f44336';
  }

  /**
   * 获取健康风险颜色
   */
  private getRiskColor(risk: HealthRisk): string {
    const colors: Record<string, string> = {
      '无风险': '#4caf50', '低风险': '#8bc34a',
      '中等风险': '#ff9800', '高风险': '#f44336', '极高风险': '#b71c1c'
    };
    return colors[risk] || '#888888';
  }

  build() {
    Scroll() {
      Column() {
        // 标题
        Text('智能环境监测')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#e0e0e0')
          .margin({ bottom: 24 })

        if (this.assessment) {
          // 综合评分卡片
          Column() {
            Text('环境舒适度评分')
              .fontSize(14)
              .fontColor('#888888')
            Text(`${this.assessment.score}`)
              .fontSize(64)
              .fontWeight(FontWeight.Bold)
              .fontColor(this.getScoreColor(this.assessment.score))
            Text(this.assessment.level)
              .fontSize(18)
              .fontColor(this.getScoreColor(this.assessment.score))
              .margin({ top: 4 })
          }
          .width('90%')
          .padding(24)
          .borderRadius(16)
          .backgroundColor('#1a1a2e')
          .margin({ bottom: 16 })

          // 温湿度评分对比
          Row() {
            Column() {
              Text('温度评分')
                .fontSize(12)
                .fontColor('#888888')
              Text(`${this.assessment.tempScore}`)
                .fontSize(28)
                .fontWeight(FontWeight.Bold)
                .fontColor('#e94560')
              Text(`${this.temperature}°C`)
                .fontSize(12)
                .fontColor('#888888')
            }
            .layoutWeight(1)

            Column() {
              Text('湿度评分')
                .fontSize(12)
                .fontColor('#888888')
              Text(`${this.assessment.humidityScore}`)
                .fontSize(28)
                .fontWeight(FontWeight.Bold)
                .fontColor('#533483')
              Text(`${this.humidity}%RH`)
                .fontSize(12)
                .fontColor('#888888')
            }
            .layoutWeight(1)

            Column() {
              Text('体感温度')
                .fontSize(12)
                .fontColor('#888888')
              Text(`${this.assessment.apparentTemp}`)
                .fontSize(28)
                .fontWeight(FontWeight.Bold)
                .fontColor('#0f3460')
              Text('°C')
                .fontSize(12)
                .fontColor('#888888')
            }
            .layoutWeight(1)
          }
          .width('90%')
          .padding(20)
          .borderRadius(16)
          .backgroundColor('#1a1a2e')
          .margin({ bottom: 16 })

          // 健康风险
          Row() {
            Text('健康风险: ')
              .fontSize(14)
              .fontColor('#888888')
            Text(this.assessment.healthRisk)
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor(this.getRiskColor(this.assessment.healthRisk))
          }
          .width('90%')
          .padding(12)
          .borderRadius(8)
          .backgroundColor('#1a1a2e')
          .margin({ bottom: 16 })

          // 改善建议
          Column() {
            Text('改善建议')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor('#e0e0e0')
              .margin({ bottom: 8 })
            ForEach(this.assessment.suggestions, (suggestion: string) => {
              Text(`${suggestion}`)
                .fontSize(13)
                .fontColor('#cccccc')
                .width('100%')
                .margin({ bottom: 4 })
            })
          }
          .width('90%')
          .padding(16)
          .borderRadius(12)
          .backgroundColor('#1a1a2e')
          .alignItems(HorizontalAlign.Start)
          .margin({ bottom: 16 })

          // 场景建议
          Text(this.sceneRecommendation)
            .fontSize(14)
            .fontColor('#e0e0e0')
            .width('90%')
            .padding(16)
            .borderRadius(12)
            .backgroundColor('#1a2a1e')
            .margin({ bottom: 16 })
        }
      }
      .width('100%')
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0a0a1a')
  }
}

四、踩坑与注意事项

4.1 传感器可用性与兼容性

问题 原因 解决方案
湿度传感器不可用 大多数手机没有独立湿度传感器 使用外接蓝牙/WiFi温湿度传感器,或使用云端天气API
温湿度数据时间不对齐 两个传感器采样频率不同 使用时间窗口对齐,取最近N秒内的数据
湿度数据跳变 传感器噪声或遮挡 使用中值滤波或EMA平滑
模拟器无湿度数据 模拟器不模拟湿度传感器 使用Mock数据或真机测试

4.2 计算精度问题

  • Magnus公式精度:在-40°C~+50°C范围内精度为±0.35°C,超出范围误差增大
  • 绝对湿度计算:需要使用饱和水蒸气压,不同公式精度不同
  • 浮点运算精度:ArkTS的number类型为64位浮点,一般精度足够
  • 显示精度:湿度保留1位小数,温度保留1位小数,露点温度保留1位小数

4.3 外接传感器方案

当设备本身没有湿度传感器时,可以通过蓝牙或WiFi连接外部传感器模块:

// 蓝牙温湿度传感器数据接收(示意)
import socket from '@ohos.net.socket';
import { BusinessError } from '@ohos.base';

// 外接传感器数据解析器
class ExternalSensorParser {
  /**
   * 解析蓝牙传感器数据帧
   * 帧格式:[0xAA, 0x55, temp_h, temp_l, hum_h, hum_l, checksum]
   */
  parseDataFrame(buffer: ArrayBuffer): { temperature: number; humidity: number } | null {
    const dataView = new DataView(buffer);

    // 验证帧头
    if (dataView.getUint8(0) !== 0xAA || dataView.getUint8(1) !== 0x55) {
      return null; // 无效帧头
    }

    // 读取温度(0.1°C精度,有符号16位)
    const rawTemp = dataView.getInt16(2, true); // 小端序
    const temperature = rawTemp / 10;

    // 读取湿度(0.1%RH精度,无符号16位)
    const rawHumidity = dataView.getUint16(4, true);
    const humidity = rawHumidity / 10;

    // 校验和验证
    let checksum = 0;
    for (let i = 0; i < 6; i++) {
      checksum += dataView.getUint8(i);
    }
    checksum = checksum & 0xFF;
    if (checksum !== dataView.getUint8(6)) {
      return null; // 校验失败
    }

    // 数据范围验证
    if (temperature < -40 || temperature > 85 || humidity < 0 || humidity > 100) {
      return null; // 超出有效范围
    }

    return { temperature, humidity };
  }
}

4.4 性能优化建议

  • 传感器采样率不宜过高:环境温湿度变化缓慢,1秒采样一次足够
  • 使用节流控制更新频率:避免频繁重新计算舒适度
  • 历史数据使用环形缓冲区:避免数组频繁扩容
  • 复杂计算使用Worker线程:Heat Index等计算可移至后台线程

五、HarmonyOS 6适配

5.1 新版传感器API变化

变化项 HarmonyOS 5 HarmonyOS 6
湿度传感器 SensorType.HUMIDITY SensorType.RELATIVE_HUMIDITY(更精确命名)
数据结构 {humidity, timestamp} {relativeHumidity, absoluteHumidity, dewPoint, timestamp}
新增功能 - 传感器硬件校准接口、自检功能
批量订阅 需分别订阅温湿度 sensor.subscribeEnvironment() 批量订阅

5.2 HarmonyOS 6环境传感器批量订阅

// HarmonyOS 6 批量环境传感器订阅
import sensor from '@ohos.sensor';

// 新版环境传感器数据结构
interface EnvironmentSensorData {
  relativeHumidity: number;    // 相对湿度(%)
  absoluteHumidity: number;    // 绝对湿度(g/m³)
  dewPoint: number;            // 露点温度(°C)
  temperature: number;         // 环境温度(°C)
  pressure: number;            // 气压(hPa)
  illuminance: number;         // 光照强度(Lux)
  timestamp: number;           // 时间戳
}

class EnvironmentSensorManagerV6 {
  private subscriberId: number = -1;

  /**
   * 批量订阅环境传感器
   * HarmonyOS 6 新增接口,一次订阅获取所有环境参数
   */
  subscribeEnvironment(
    callback: (data: EnvironmentSensorData) => void
  ): void {
    try {
      // 检查新版API是否可用
      if (typeof sensor.subscribeEnvironment === 'function') {
        this.subscriberId = sensor.subscribeEnvironment(
          (data: EnvironmentSensorData) => {
            callback(data);
          },
          { samplingPeriod: 1000000000 } // 1秒
        );
      } else {
        // 降级到分别订阅
        this.fallbackSubscribe(callback);
      }
    } catch (error) {
      console.error('环境传感器订阅失败');
    }
  }

  /**
   * 降级方案:分别订阅各传感器
   */
  private fallbackSubscribe(
    callback: (data: EnvironmentSensorData) => void
  ): void {
    let humidity = 50;
    let temperature = 25;
    let pressure = 1013;
    let illuminance = 300;

    // 分别订阅并汇总
    sensor.on(sensor.SensorType.HUMIDITY, (data) => {
      humidity = data.humidity;
      callback(this.buildFallbackData(humidity, temperature, pressure, illuminance));
    });

    sensor.on(sensor.SensorType.AMBIENT_TEMPERATURE, (data) => {
      temperature = data.temperature;
      callback(this.buildFallbackData(humidity, temperature, pressure, illuminance));
    });
  }

  private buildFallbackData(
    rh: number, temp: number, pressure: number, lux: number
  ): EnvironmentSensorData {
    return {
      relativeHumidity: rh,
      absoluteHumidity: 0, // 需自行计算
      dewPoint: 0,         // 需自行计算
      temperature: temp,
      pressure: pressure,
      illuminance: lux,
      timestamp: Date.now() * 1000000
    };
  }
}

5.3 HarmonyOS 6传感器硬件校准

HarmonyOS 6新增了传感器硬件校准接口,可以修正传感器漂移:

  • 湿度传感器校准:使用饱和盐溶液法(如NaCl溶液在25°C时RH=75%)进行两点校准
  • 温度传感器校准:使用冰水混合物(0°C)和沸水(100°C)进行两点校准
  • 校准数据持久化:校准参数存储在安全分区,重启后保持有效

六、总结

6.1 核心技术要点回顾

技术要点 关键实现
湿度传感器订阅 sensor.on(SensorType.HUMIDITY, callback)
露点温度计算 Magnus公式:T_d = b·α/(a-α)
绝对湿度计算 AH = 216.7·(RH/100)·P_sat/(273.15+T)
温湿指数 THI = T - (0.55-0.0055·RH)·(T-14.5)
舒适度评估 温度评分(60%) + 湿度评分(40%) 综合加权
健康风险评估 基于极端温湿度和霉菌风险的多维度判定

6.2 最佳实践清单

  1. ✅ 同时订阅温度和湿度传感器,确保数据时间对齐
  2. ✅ 使用Magnus公式计算露点温度和绝对湿度
  3. ✅ 实现THI温湿指数评估环境舒适度
  4. ✅ 使用高斯函数计算温湿度评分,更符合人体感知
  5. ✅ 实现健康风险评估,提供改善建议
  6. ✅ 传感器不可用时提供外接传感器或云端API降级方案
  7. ✅ 使用节流控制更新频率,避免频繁计算
  8. ✅ 适配HarmonyOS 6批量环境传感器订阅

6.3 扩展方向

  • 智能家居联动:通过HarmonyOS Connect控制加湿器、空调等设备
  • 环境数据云同步:多设备环境数据汇总,构建家庭环境地图
  • AI环境预测:基于历史数据预测环境变化趋势
  • 健康档案:长期记录用户所处环境数据,关联健康指标
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。