HarmonyOS APP开发红外遥控:IR发射与学习
【摘要】 HarmonyOS APP开发红外遥控:IR发射与学习还记得以前满茶几的遥控器吗?电视、空调、机顶盒、音响……现在,一部手机就能搞定所有。这就是红外遥控(IR Remote)技术的功劳。今天咱们就来聊聊鸿蒙系统中的红外遥控开发,看看如何让你的应用具备"万能遥控"的能力。 一、背景与动机 1.1 红外遥控的应用场景红外遥控虽然"古老",但在智能家居领域依然广泛应用:应用场景设备类型特点电视控...
HarmonyOS APP开发红外遥控:IR发射与学习
还记得以前满茶几的遥控器吗?电视、空调、机顶盒、音响……现在,一部手机就能搞定所有。这就是红外遥控(IR Remote)技术的功劳。今天咱们就来聊聊鸿蒙系统中的红外遥控开发,看看如何让你的应用具备"万能遥控"的能力。
一、背景与动机
1.1 红外遥控的应用场景
红外遥控虽然"古老",但在智能家居领域依然广泛应用:
| 应用场景 | 设备类型 | 特点 |
|---|---|---|
| 电视控制 | 电视、投影仪 | 最常见应用 |
| 空调控制 | 各品牌空调 | 复杂编码 |
| 机顶盒 | IPTV、有线电视 | 标准编码 |
| 音响系统 | 功放、音响 | 多设备控制 |
| 智能家居 | 窗帘、灯光 | 自定义编码 |
1.2 红外遥控原理
红外遥控基于红外线(波长940nm左右)的脉冲调制:

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编码流程图:

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);
六、总结
红外遥控虽然是一项"古老"的技术,但在智能家居领域依然发挥着重要作用。本文全面讲解了鸿蒙系统中红外遥控开发的核心要点:
核心要点回顾:
- 编码格式:理解NEC、RC-5等常见编码格式
- 脉冲生成:正确生成脉冲序列,注意时序精度
- 红外学习:接收红外信号,分析识别编码
- 编码库管理:预定义常见设备编码,支持自定义学习
- 发射优化:注意距离、角度、重复发射等
下一步学习:
- USB通信技术(下一篇文章)
- 红外与RFID对比
- 智能家居协议集成
- 多设备联动控制
红外遥控看似简单,但要做好"万能遥控"需要积累大量的编码数据。希望本文能帮助你掌握红外开发的核心技能,让你的应用具备控制家电的能力!
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)