HarmonyOS APP开发红外遥控:IR发射与学习

举报
Jack20 发表于 2026/06/19 20:21:23 2026/06/19
【摘要】 HarmonyOS APP开发红外遥控:IR发射与学习还记得以前满茶几的遥控器吗?电视、空调、机顶盒、音响……现在,一部手机就能搞定所有。这就是红外遥控(IR Remote)技术的功劳。今天咱们就来聊聊鸿蒙系统中的红外遥控开发,看看如何让你的应用具备"万能遥控"的能力。 一、背景与动机 1.1 红外遥控的应用场景红外遥控虽然"古老",但在智能家居领域依然广泛应用:应用场景设备类型特点电视控...

HarmonyOS APP开发红外遥控:IR发射与学习

还记得以前满茶几的遥控器吗?电视、空调、机顶盒、音响……现在,一部手机就能搞定所有。这就是红外遥控(IR Remote)技术的功劳。今天咱们就来聊聊鸿蒙系统中的红外遥控开发,看看如何让你的应用具备"万能遥控"的能力。

一、背景与动机

1.1 红外遥控的应用场景

红外遥控虽然"古老",但在智能家居领域依然广泛应用:

应用场景 设备类型 特点
电视控制 电视、投影仪 最常见应用
空调控制 各品牌空调 复杂编码
机顶盒 IPTV、有线电视 标准编码
音响系统 功放、音响 多设备控制
智能家居 窗帘、灯光 自定义编码

1.2 红外遥控原理

红外遥控基于红外线(波长940nm左右)的脉冲调制:
图片.png

1.3 红外编码格式

红外遥控有多种编码格式,最常见的是NEC和RC-5:

编码格式 载波频率 特点 应用设备
NEC 38kHz 最常见,可靠性高 电视、机顶盒
RC-5 36kHz 飞利浦标准 欧洲设备
RC-6 36kHz RC-5扩展 高端设备
Samsung 38kHz 三星专用 三星设备
Sony 40kHz 索尼SIRC 索尼设备
Raw 自定义 原始脉冲 空调等

二、核心原理

2.1 NEC编码详解

NEC是最常见的红外编码格式,理解它对开发红外遥控至关重要:

// NEC编码结构
interface NecCode {
  // 引导码:9ms高电平 + 4.5ms低电平
  header: number[];
  
  // 地址码:8位
  address: number;
  
  // 地址反码:8位
  addressInverse: number;
  
  // 命令码:8位
  command: number;
  
  // 命令反码:8位
  commandInverse: number;
  
  // 结束码:560us高电平
  end: number[];
}

// NEC时序定义
const NEC_TIMING = {
  // 载波频率
  CARRIER_FREQUENCY: 38000,
  
  // 引导码
  HEADER_MARK: 9000,    // 9ms高电平
  HEADER_SPACE: 4500,   // 4.5ms低电平
  
  // 重复码
  REPEAT_MARK: 9000,
  REPEAT_SPACE: 2250,
  
  // 数据位
  BIT_MARK: 562,        // 560us高电平
  ONE_SPACE: 1687,      // 1.687ms(逻辑1)
  ZERO_SPACE: 562,      // 560us(逻辑0)
  
  // 结束位
  END_MARK: 562
};

NEC编码流程图:
图片.png

2.2 脉冲序列表示

红外信号用脉冲序列表示,每个脉冲包含高电平和低电平的持续时间:

// 脉冲序列
interface PulseSequence {
  // 载波频率(Hz)
  carrierFrequency: number;
  
  // 脉冲数组(微秒)
  // 奇数索引:高电平持续时间
  // 偶数索引:低电平持续时间
  pulses: number[];
}

// 示例:NEC编码的脉冲序列
const necExample: PulseSequence = {
  carrierFrequency: 38000,
  pulses: [
    9000, 4500,  // 引导码
    562, 562,    // 地址位0
    562, 1687,   // 地址位1
    // ... 更多位
    562          // 结束位
  ]
};

2.3 红外学习原理

红外学习是通过接收红外信号,测量脉冲宽度,生成编码:

flowchart TD
    A[开始学习] --> B[等待红外信号]
    B --> C[检测到信号]
    C --> D[测量引导码]
    D --> E[测量数据位]
    E --> F{信号结束?}
    F -->|| E
    F -->|| G[分析脉冲序列]
    G --> H{识别编码格式}
    H -->|NEC| I[生成NEC编码]
    H -->|RC-5| J[生成RC-5编码]
    H -->|未知| K[保存原始脉冲]
    I --> L[保存到编码库]
    J --> L
    K --> L
    L --> M[学习完成]
    
    classDef process fill:#4A90E2,stroke:#2E5B8C,color:#fff
    classDef analyze fill:#F5A623,stroke:#C17A00,color:#fff
    classDef result fill:#4CAF50,stroke:#388E3C,color:#fff
    
    class A,B,C,D,E,F process
    class G,H analyze
    class I,J,K,L,M result

三、代码实战

3.1 红外发射核心类封装

import infrared from '@ohos.infrared';
import { BusinessError } from '@ohos.base';

/**
 * 红外编码类型
 */
export enum IrProtocol {
  NEC = 'NEC',
  RC5 = 'RC-5',
  RC6 = 'RC-6',
  SAMSUNG = 'Samsung',
  SONY = 'Sony',
  RAW = 'Raw'
}

/**
 * 红外编码数据
 */
export interface IrCode {
  // 编码类型
  protocol: IrProtocol;
  
  // 地址码
  address: number;
  
  // 命令码
  command: number;
  
  // 原始脉冲(RAW模式使用)
  rawPulses?: number[];
  
  // 载波频率
  carrierFrequency?: number;
  
  // 描述
  description?: string;
}

/**
 * 红外遥控管理器
 * 封装红外发射、学习等功能
 */
export class IrRemoteManager {
  private static instance: IrRemoteManager;
  
  // 红外发射器
  private emitter: infrared.IrEmitter | null = null;
  
  // 红外接收器(用于学习)
  private receiver: infrared.IrReceiver | null = null;
  
  // 编码库
  private codeLibrary: Map<string, IrCode[]> = new Map();

  private constructor() {}

  /**
   * 获取单例实例
   */
  public static getInstance(): IrRemoteManager {
    if (!IrRemoteManager.instance) {
      IrRemoteManager.instance = new IrRemoteManager();
    }
    return IrRemoteManager.instance;
  }

  /**
   * 初始化红外
   */
  public async init(): Promise<boolean> {
    try {
      // 创建红外发射器
      this.emitter = infrared.createIrEmitter();
      
      console.info('[红外] 初始化成功');
      return true;
    } catch (error) {
      console.error('[红外] 初始化失败:', error);
      return false;
    }
  }

  /**
   * 检查是否支持红外
   */
  public async hasIrEmitter(): Promise<boolean> {
    try {
      return infrared.hasIrEmitter();
    } catch (error) {
      return false;
    }
  }

  /**
   * 发射NEC编码
   * @param address 地址码
   * @param command 命令码
   */
  public async transmitNec(address: number, command: number): Promise<boolean> {
    if (!this.emitter) {
      console.error('[红外] 发射器未初始化');
      return false;
    }

    try {
      // 生成NEC脉冲序列
      const pulses = this.generateNecPulses(address, command);
      
      // 发射
      await this.emitter.transmit({
        carrierFrequency: 38000,
        pulses: pulses
      });
      
      console.info(`[红外] NEC发射: 地址=${address}, 命令=${command}`);
      return true;
    } catch (error) {
      console.error('[红外] NEC发射失败:', error);
      return false;
    }
  }

  /**
   * 生成NEC脉冲序列
   */
  private generateNecPulses(address: number, command: number): number[] {
    const pulses: number[] = [];
    
    // 引导码
    pulses.push(NEC_TIMING.HEADER_MARK);
    pulses.push(NEC_TIMING.HEADER_SPACE);
    
    // 地址码(8位)
    for (let i = 0; i < 8; i++) {
      pulses.push(NEC_TIMING.BIT_MARK);
      pulses.push((address >> i) & 1 ? NEC_TIMING.ONE_SPACE : NEC_TIMING.ZERO_SPACE);
    }
    
    // 地址反码(8位)
    const addressInverse = ~address & 0xFF;
    for (let i = 0; i < 8; i++) {
      pulses.push(NEC_TIMING.BIT_MARK);
      pulses.push((addressInverse >> i) & 1 ? NEC_TIMING.ONE_SPACE : NEC_TIMING.ZERO_SPACE);
    }
    
    // 命令码(8位)
    for (let i = 0; i < 8; i++) {
      pulses.push(NEC_TIMING.BIT_MARK);
      pulses.push((command >> i) & 1 ? NEC_TIMING.ONE_SPACE : NEC_TIMING.ZERO_SPACE);
    }
    
    // 命令反码(8位)
    const commandInverse = ~command & 0xFF;
    for (let i = 0; i < 8; i++) {
      pulses.push(NEC_TIMING.BIT_MARK);
      pulses.push((commandInverse >> i) & 1 ? NEC_TIMING.ONE_SPACE : NEC_TIMING.ZERO_SPACE);
    }
    
    // 结束位
    pulses.push(NEC_TIMING.END_MARK);
    
    return pulses;
  }

  /**
   * 发射原始脉冲
   * @param carrierFrequency 载波频率
   * @param pulses 脉冲序列
   */
  public async transmitRaw(carrierFrequency: number, pulses: number[]): Promise<boolean> {
    if (!this.emitter) {
      console.error('[红外] 发射器未初始化');
      return false;
    }

    try {
      await this.emitter.transmit({
        carrierFrequency: carrierFrequency,
        pulses: pulses
      });
      
      console.info(`[红外] 原始脉冲发射: ${pulses.length} 个脉冲`);
      return true;
    } catch (error) {
      console.error('[红外] 原始脉冲发射失败:', error);
      return false;
    }
  }

  /**
   * 发射红外编码
   */
  public async transmitCode(code: IrCode): Promise<boolean> {
    switch (code.protocol) {
      case IrProtocol.NEC:
        return await this.transmitNec(code.address, code.command);
      
      case IrProtocol.RAW:
        if (code.rawPulses && code.carrierFrequency) {
          return await this.transmitRaw(code.carrierFrequency, code.rawPulses);
        }
        return false;
      
      default:
        console.warn(`[红外] 不支持的协议: ${code.protocol}`);
        return false;
    }
  }

  /**
   * 开始红外学习
   */
  public async startLearning(
    onReceived: (pulses: number[]) => void
  ): Promise<boolean> {
    try {
      // 创建接收器
      this.receiver = infrared.createIrReceiver();
      
      // 设置接收回调
      this.receiver.on('receive', (data: infrared.IrReceiveData) => {
        console.info(`[红外学习] 收到信号,${data.pulses.length} 个脉冲`);
        onReceived(data.pulses);
      });
      
      console.info('[红外学习] 开始');
      return true;
    } catch (error) {
      console.error('[红外学习] 启动失败:', error);
      return false;
    }
  }

  /**
   * 停止红外学习
   */
  public async stopLearning(): Promise<void> {
    if (this.receiver) {
      this.receiver.off('receive');
      this.receiver = null;
      console.info('[红外学习] 停止');
    }
  }

  /**
   * 分析脉冲序列
   * 尝试识别编码格式
   */
  public analyzePulses(pulses: number[]): IrCode | null {
    // 检查是否为NEC编码
    const necCode = this.tryParseNec(pulses);
    if (necCode) {
      return necCode;
    }
    
    // 检查其他编码格式...
    
    // 无法识别,返回原始脉冲
    return {
      protocol: IrProtocol.RAW,
      address: 0,
      command: 0,
      rawPulses: pulses,
      carrierFrequency: 38000,
      description: '未识别的编码'
    };
  }

  /**
   * 尝试解析NEC编码
   */
  private tryParseNec(pulses: number[]): IrCode | null {
    // NEC至少需要67个脉冲(引导码 + 32位数据 + 结束位)
    if (pulses.length < 67) {
      return null;
    }
    
    // 检查引导码
    if (!this.isWithinTolerance(pulses[0], NEC_TIMING.HEADER_MARK, 0.2) ||
        !this.isWithinTolerance(pulses[1], NEC_TIMING.HEADER_SPACE, 0.2)) {
      return null;
    }
    
    // 解析数据位
    let address = 0;
    let command = 0;
    
    // 解析地址码
    for (let i = 0; i < 8; i++) {
      const markIndex = 2 + i * 2;
      const spaceIndex = markIndex + 1;
      
      if (this.isWithinTolerance(pulses[spaceIndex], NEC_TIMING.ONE_SPACE, 0.2)) {
        address |= (1 << i);
      }
    }
    
    // 跳过地址反码,解析命令码
    for (let i = 0; i < 8; i++) {
      const markIndex = 2 + 16 * 2 + i * 2;
      const spaceIndex = markIndex + 1;
      
      if (this.isWithinTolerance(pulses[spaceIndex], NEC_TIMING.ONE_SPACE, 0.2)) {
        command |= (1 << i);
      }
    }
    
    return {
      protocol: IrProtocol.NEC,
      address: address,
      command: command,
      description: `NEC编码: 地址=${address.toString(16)}, 命令=${command.toString(16)}`
    };
  }

  /**
   * 检查是否在容差范围内
   */
  private isWithinTolerance(value: number, target: number, tolerance: number): boolean {
    return Math.abs(value - target) / target <= tolerance;
  }

  /**
   * 保存编码到库
   */
  public saveCode(deviceName: string, buttonName: string, code: IrCode): void {
    if (!this.codeLibrary.has(deviceName)) {
      this.codeLibrary.set(deviceName, []);
    }
    
    const codes = this.codeLibrary.get(deviceName)!;
    codes.push({
      ...code,
      description: buttonName
    });
    
    console.info(`[红外] 保存编码: ${deviceName} - ${buttonName}`);
  }

  /**
   * 获取设备的编码列表
   */
  public getDeviceCodes(deviceName: string): IrCode[] {
    return this.codeLibrary.get(deviceName) || [];
  }

  /**
   * 获取所有设备
   */
  public getAllDevices(): string[] {
    return Array.from(this.codeLibrary.keys());
  }
}

3.2 电视遥控器示例

import { IrRemoteManager, IrProtocol, IrCode } from './IrRemoteManager';

/**
 * 电视遥控器
 * 预定义常见电视品牌的编码
 */
export class TvRemoteController {
  private irManager: IrRemoteManager = IrRemoteManager.getInstance();
  
  // 当前电视品牌
  private currentBrand: string = '';
  
  // 电视编码库
  private tvCodes: Map<string, Map<string, IrCode>> = new Map();

  constructor() {
    this.initTvCodes();
  }

  /**
   * 初始化电视编码库
   */
  private initTvCodes(): void {
    // 三星电视
    const samsungCodes = new Map<string, IrCode>();
    samsungCodes.set('POWER', { protocol: IrProtocol.NEC, address: 0x07, command: 0x02 });
    samsungCodes.set('VOLUME_UP', { protocol: IrProtocol.NEC, address: 0x07, command: 0x01 });
    samsungCodes.set('VOLUME_DOWN', { protocol: IrProtocol.NEC, address: 0x07, command: 0x00 });
    samsungCodes.set('CHANNEL_UP', { protocol: IrProtocol.NEC, address: 0x07, command: 0x12 });
    samsungCodes.set('CHANNEL_DOWN', { protocol: IrProtocol.NEC, address: 0x07, command: 0x13 });
    samsungCodes.set('MUTE', { protocol: IrProtocol.NEC, address: 0x07, command: 0x0F });
    samsungCodes.set('INPUT', { protocol: IrProtocol.NEC, address: 0x07, command: 0x58 });
    this.tvCodes.set('Samsung', samsungCodes);

    // 索尼电视
    const sonyCodes = new Map<string, IrCode>();
    sonyCodes.set('POWER', { protocol: IrProtocol.NEC, address: 0x00, command: 0x15 });
    sonyCodes.set('VOLUME_UP', { protocol: IrProtocol.NEC, address: 0x00, command: 0x14 });
    sonyCodes.set('VOLUME_DOWN', { protocol: IrProtocol.NEC, address: 0x00, command: 0x13 });
    sonyCodes.set('CHANNEL_UP', { protocol: IrProtocol.NEC, address: 0x00, command: 0x10 });
    sonyCodes.set('CHANNEL_DOWN', { protocol: IrProtocol.NEC, address: 0x00, command: 0x11 });
    sonyCodes.set('MUTE', { protocol: IrProtocol.NEC, address: 0x00, command: 0x16 });
    this.tvCodes.set('Sony', sonyCodes);

    // LG电视
    const lgCodes = new Map<string, IrCode>();
    lgCodes.set('POWER', { protocol: IrProtocol.NEC, address: 0x04, command: 0x08 });
    lgCodes.set('VOLUME_UP', { protocol: IrProtocol.NEC, address: 0x04, command: 0x02 });
    lgCodes.set('VOLUME_DOWN', { protocol: IrProtocol.NEC, address: 0x04, command: 0x03 });
    lgCodes.set('CHANNEL_UP', { protocol: IrProtocol.NEC, address: 0x04, command: 0x00 });
    lgCodes.set('CHANNEL_DOWN', { protocol: IrProtocol.NEC, address: 0x04, command: 0x01 });
    lgCodes.set('MUTE', { protocol: IrProtocol.NEC, address: 0x04, command: 0x09 });
    this.tvCodes.set('LG', lgCodes);
  }

  /**
   * 设置电视品牌
   */
  public setBrand(brand: string): void {
    this.currentBrand = brand;
    console.info(`[电视] 品牌: ${brand}`);
  }

  /**
   * 获取支持的电视品牌
   */
  public getSupportedBrands(): string[] {
    return Array.from(this.tvCodes.keys());
  }

  /**
   * 发送按键
   */
  public async sendKey(key: string): Promise<boolean> {
    if (!this.currentBrand) {
      console.error('[电视] 未设置品牌');
      return false;
    }

    const brandCodes = this.tvCodes.get(this.currentBrand);
    if (!brandCodes) {
      console.error(`[电视] 不支持的品牌: ${this.currentBrand}`);
      return false;
    }

    const code = brandCodes.get(key);
    if (!code) {
      console.error(`[电视] 不支持的按键: ${key}`);
      return false;
    }

    return await this.irManager.transmitCode(code);
  }

  /**
   * 开关机
   */
  public async power(): Promise<boolean> {
    return await this.sendKey('POWER');
  }

  /**
   * 音量加
   */
  public async volumeUp(): Promise<boolean> {
    return await this.sendKey('VOLUME_UP');
  }

  /**
   * 音量减
   */
  public async volumeDown(): Promise<boolean> {
    return await this.sendKey('VOLUME_DOWN');
  }

  /**
   * 频道加
   */
  public async channelUp(): Promise<boolean> {
    return await this.sendKey('CHANNEL_UP');
  }

  /**
   * 频道减
   */
  public async channelDown(): Promise<boolean> {
    return await this.sendKey('CHANNEL_DOWN');
  }

  /**
   * 静音
   */
  public async mute(): Promise<boolean> {
    return await this.sendKey('MUTE');
  }

  /**
   * 输入源切换
   */
  public async input(): Promise<boolean> {
    return await this.sendKey('INPUT');
  }
}

3.3 万能遥控器UI

import { IrRemoteManager, IrCode } from './IrRemoteManager';
import { TvRemoteController } from './TvRemoteController';

@Entry
@Component
struct UniversalRemotePage {
  private irManager: IrRemoteManager = IrRemoteManager.getInstance();
  private tvController: TvRemoteController = new TvRemoteController();
  
  @State hasIr: boolean = false;
  @State selectedBrand: string = 'Samsung';
  @State isLearning: boolean = false;
  @State learningButton: string = '';
  @State learnedPulses: number[] = [];
  @State customDevices: string[] = [];

  aboutToAppear(): void {
    this.initIr();
  }

  /**
   * 初始化红外
   */
  async initIr(): Promise<void> {
    this.hasIr = await this.irManager.hasIrEmitter();
    
    if (this.hasIr) {
      await this.irManager.init();
      this.customDevices = this.irManager.getAllDevices();
    }
  }

  /**
   * 开始学习按键
   */
  async startLearningButton(buttonName: string): Promise<void> {
    this.isLearning = true;
    this.learningButton = buttonName;
    this.learnedPulses = [];
    
    await this.irManager.startLearning((pulses: number[]) => {
      this.learnedPulses = pulses;
      this.isLearning = false;
    });
  }

  /**
   * 保存学习的按键
   */
  async saveLearnedButton(): Promise<void> {
    if (this.learnedPulses.length === 0) return;
    
    const code = this.irManager.analyzePulses(this.learnedPulses);
    if (code) {
      this.irManager.saveCode('CustomDevice', this.learningButton, code);
      this.customDevices = this.irManager.getAllDevices();
    }
    
    this.learningButton = '';
    this.learnedPulses = [];
  }

  /**
   * 发送按键
   */
  async sendKey(key: string): Promise<void> {
    if (this.selectedBrand === 'CustomDevice') {
      const codes = this.irManager.getDeviceCodes('CustomDevice');
      const code = codes.find(c => c.description === key);
      if (code) {
        await this.irManager.transmitCode(code);
      }
    } else {
      this.tvController.setBrand(this.selectedBrand);
      await this.tvController.sendKey(key);
    }
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('万能遥控')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')

        Blank()

        if (this.hasIr) {
          Text('✓ 红外')
            .fontSize(14)
            .fontColor('#4CAF50')
        } else {
          Text('✗ 无红外')
            .fontSize(14)
            .fontColor('#FF5722')
        }
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#1A1A2E')

      if (this.hasIr) {
        // 品牌选择
        Column() {
          Text('选择设备')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .width('100%')
            .margin({ bottom: 10 })

          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.tvController.getSupportedBrands(), (brand: string) => {
              Button(brand)
                .fontSize(14)
                .height(40)
                .backgroundColor(this.selectedBrand === brand ? '#4A90E2' : '#333333')
                .margin({ right: 10, bottom: 10 })
                .onClick(() => {
                  this.selectedBrand = brand;
                })
            })
            
            if (this.customDevices.length > 0) {
              Button('自定义')
                .fontSize(14)
                .height(40)
                .backgroundColor(this.selectedBrand === 'CustomDevice' ? '#4A90E2' : '#333333')
                .margin({ right: 10, bottom: 10 })
                .onClick(() => {
                  this.selectedBrand = 'CustomDevice';
                })
            }
          }
          .width('100%')
        }
        .width('90%')
        .padding(15)
        .backgroundColor('#16213E')
        .borderRadius(12)
        .margin({ top: 20 })

        // 学习模式提示
        if (this.isLearning) {
          Column() {
            Text(`正在学习: ${this.learningButton}`)
              .fontSize(16)
              .fontColor('#F5A623')

            Text('请按下遥控器按键')
              .fontSize(14)
              .fontColor('#AAAAAA')
              .margin({ top: 10 })

            LoadingProgress()
              .width(40)
              .height(40)
              .margin({ top: 15 })
          }
          .width('90%')
          .padding(20)
          .backgroundColor('#2A1A3E')
          .borderRadius(12)
          .margin({ top: 20 })
        } else if (this.learnedPulses.length > 0) {
          Column() {
            Text('学习成功!')
              .fontSize(16)
              .fontColor('#4CAF50')

            Text(`脉冲数: ${this.learnedPulses.length}`)
              .fontSize(14)
              .fontColor('#AAAAAA')
              .margin({ top: 10 })

            Button('保存')
              .width('100%')
              .height(40)
              .backgroundColor('#4A90E2')
              .margin({ top: 15 })
              .onClick(() => {
                this.saveLearnedButton();
              })
          }
          .width('90%')
          .padding(20)
          .backgroundColor('#16213E')
          .borderRadius(12)
          .margin({ top: 20 })
        }

        // 遥控器面板
        Column() {
          // 电源键
          Button('⏻ 电源')
            .width(80)
            .height(80)
            .fontSize(24)
            .backgroundColor('#E74C3C')
            .borderRadius(40)
            .onClick(() => {
              this.sendKey('POWER');
            })
            .margin({ bottom: 30 })

          // 音量控制
          Row() {
            Button('🔊+')
              .width(70)
              .height(70)
              .fontSize(20)
              .backgroundColor('#4A90E2')
              .onClick(() => {
                this.sendKey('VOLUME_UP');
              })

            Column() {
              Button('🔇')
                .width(50)
                .height(50)
                .fontSize(20)
                .backgroundColor('#666666')
                .onClick(() => {
                  this.sendKey('MUTE');
                })
            }
            .margin({ left: 20, right: 20 })

            Button('🔊-')
              .width(70)
              .height(70)
              .fontSize(20)
              .backgroundColor('#4A90E2')
              .onClick(() => {
                this.sendKey('VOLUME_DOWN');
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.Center)
          .margin({ bottom: 30 })

          // 频道控制
          Row() {
            Button('📺+')
              .width(70)
              .height(70)
              .fontSize(20)
              .backgroundColor('#4CAF50')
              .onClick(() => {
                this.sendKey('CHANNEL_UP');
              })

            Column() {
              Button('INPUT')
                .width(50)
                .height(50)
                .fontSize(12)
                .backgroundColor('#666666')
                .onClick(() => {
                  this.sendKey('INPUT');
                })
            }
            .margin({ left: 20, right: 20 })

            Button('📺-')
              .width(70)
              .height(70)
              .fontSize(20)
              .backgroundColor('#4CAF50')
              .onClick(() => {
                this.sendKey('CHANNEL_DOWN');
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.Center)
        }
        .width('90%')
        .padding(30)
        .backgroundColor('#16213E')
        .borderRadius(12)
        .margin({ top: 20 })

        // 学习按钮
        Button('学习新按键')
          .width('90%')
          .height(50)
          .backgroundColor('#F5A623')
          .enabled(!this.isLearning)
          .onClick(() => {
            this.startLearningButton('CustomKey');
          })
          .margin({ top: 20 })
      } else {
        // 无红外提示
        Column() {
          Text('设备不支持红外')
            .fontSize(18)
            .fontColor('#AAAAAA')
            .margin({ top: 100 })

          Text('请使用支持红外发射的设备')
            .fontSize(14)
            .fontColor('#666666')
            .margin({ top: 10 })
        }
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0F0F1A')
  }
}

四、踩坑与注意事项

4.1 权限配置

// module.json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.USE_INFRARED",
        "reason": "使用红外发射功能"
      },
      {
        "name": "ohos.permission.RECEIVE_INFRARED",
        "reason": "接收红外信号(学习模式)"
      }
    ]
  }
}

4.2 发射距离与角度

坑点:红外发射有方向性和距离限制。

/**
 * 发射提示
 */
function showEmitTip(): void {
  console.info('[红外] 发射提示:');
  console.info('1. 将手机红外头对准设备');
  console.info('2. 距离建议 10-30cm');
  console.info('3. 角度偏差不超过 30度');
  console.info('4. 避免强光干扰');
}

4.3 重复发射处理

坑点:某些设备需要重复发射才能响应。

/**
 * 重复发射
 */
async function transmitWithRepeat(
  code: IrCode,
  repeatCount: number = 3,
  interval: number = 100
): Promise<boolean> {
  for (let i = 0; i < repeatCount; i++) {
    const success = await irManager.transmitCode(code);
    if (!success) return false;
    
    if (i < repeatCount - 1) {
      await delay(interval);
    }
  }
  return true;
}

function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

4.4 学习模式超时

坑点:学习模式需要设置超时,避免一直等待。

/**
 * 带超时的学习
 */
async function learnWithTimeout(
  timeout: number = 10000
): Promise<number[] | null> {
  return new Promise(async (resolve) => {
    let resolved = false;
    
    const timer = setTimeout(() => {
      if (!resolved) {
        resolved = true;
        irManager.stopLearning();
        resolve(null);
        console.warn('[红外学习] 超时');
      }
    }, timeout);

    await irManager.startLearning((pulses: number[]) => {
      if (!resolved) {
        resolved = true;
        clearTimeout(timer);
        irManager.stopLearning();
        resolve(pulses);
      }
    });
  });
}

4.5 编码库持久化

坑点:学习的编码需要持久化存储。

import preferences from '@ohos.data.preferences';

/**
 * 保存编码库到持久化存储
 */
async function saveCodeLibrary(): Promise<void> {
  try {
    const prefs = await preferences.getPreferences('ir_codes');
    
    const library = irManager.getAllDevices();
    for (const device of library) {
      const codes = irManager.getDeviceCodes(device);
      await prefs.put(device, JSON.stringify(codes));
    }
    
    await prefs.flush();
    console.info('[红外] 编码库已保存');
  } catch (error) {
    console.error('[红外] 保存编码库失败:', error);
  }
}

/**
 * 加载编码库
 */
async function loadCodeLibrary(): Promise<void> {
  try {
    const prefs = await preferences.getPreferences('ir_codes');
    // 加载逻辑...
    console.info('[红外] 编码库已加载');
  } catch (error) {
    console.error('[红外] 加载编码库失败:', error);
  }
}

五、HarmonyOS 6适配

5.1 API变更

API HarmonyOS 5 HarmonyOS 6 说明
transmit() 单次发射 支持队列发射 可连续发射多个编码
createIrReceiver() 基础功能 支持多协议识别 自动识别编码格式
新增 - getIrCapabilities() 获取红外能力信息

适配代码

/**
 * HarmonyOS 6红外适配
 */
async function transmitAdaptive(codes: IrCode[]): Promise<boolean> {
  const apiVersion = getApiVersion();

  try {
    if (apiVersion >= 6) {
      // HarmonyOS 6 批量发射
      const pulseList = codes.map(code => ({
        carrierFrequency: code.carrierFrequency || 38000,
        pulses: generatePulses(code)
      }));
      
      return await emitter.transmitBatch(pulseList);
    } else {
      // HarmonyOS 5 逐个发射
      for (const code of codes) {
        await irManager.transmitCode(code);
      }
      return true;
    }
  } catch (error) {
    console.error('[红外] 发射失败:', error);
    return false;
  }
}

5.2 新增功能

HarmonyOS 6增强了红外能力:

// 1. 获取红外能力
const capabilities = await infrared.getIrCapabilities();
console.info(`载波频率范围: ${capabilities.minFrequency}-${capabilities.maxFrequency} Hz`);
console.info(`最大脉冲数: ${capabilities.maxPulseCount}`);

// 2. 自动识别编码
receiver.on('receive', (data: infrared.IrReceiveData) => {
  console.info(`识别的协议: ${data.protocol}`);
  console.info(`地址码: ${data.address}`);
  console.info(`命令码: ${data.command}`);
});

// 3. 发射队列
await emitter.transmitQueue([
  { carrierFrequency: 38000, pulses: pulses1, delay: 0 },
  { carrierFrequency: 38000, pulses: pulses2, delay: 100 },
  { carrierFrequency: 38000, pulses: pulses3, delay: 100 }
]);

// 4. 发射功率控制
await emitter.setTransmitPower(0.8); // 80%功率

5.3 性能优化

// 1. 预编译脉冲序列
const compiledPulses = await emitter.compilePulses(pulses);
await emitter.transmitCompiled(compiledPulses);

// 2. 脉冲缓存
emitter.enableCache(true);

// 3. 低功耗模式
await emitter.setLowPowerMode(true);

六、总结

红外遥控虽然是一项"古老"的技术,但在智能家居领域依然发挥着重要作用。本文全面讲解了鸿蒙系统中红外遥控开发的核心要点:

核心要点回顾

  1. 编码格式:理解NEC、RC-5等常见编码格式
  2. 脉冲生成:正确生成脉冲序列,注意时序精度
  3. 红外学习:接收红外信号,分析识别编码
  4. 编码库管理:预定义常见设备编码,支持自定义学习
  5. 发射优化:注意距离、角度、重复发射等

下一步学习

  • USB通信技术(下一篇文章)
  • 红外与RFID对比
  • 智能家居协议集成
  • 多设备联动控制

红外遥控看似简单,但要做好"万能遥控"需要积累大量的编码数据。希望本文能帮助你掌握红外开发的核心技能,让你的应用具备控制家电的能力!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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