一直想通过手机自己设计一个空调遥控器,而安卓开放了相关的API供开发者控制红外线发射,所以自己学了下相关知识
1.基本前置知识(并不全,没接触过红外线的建议看专门介绍红外线的帖子,不感兴趣可以跳过):
红外线技术可以通过发送调制的红外线光来与拥有红外线接收装置的设备进行控制。家用电器一般使用38KHz的调制频率,即红外线以38KHz的频率闪烁,此时对接收装置来说,就可以收到光信号,从而完成更复杂的通信。
(其实不使用38KHz的调制信号也可以完成通信,比如让红外线一直亮着,但是,38KHz的信号在有外界干扰时会表现出更好的抗干扰性。)
2.安卓控制红外线
首先在AndroidManifest文件中声明权限:
<uses-permission android:name="android.permission.TRANSMIT_IR" />
如果只允许在支持红外线的设备上运行此应用,则还可以添加:
<uses-feature android:name="android.hardware.ConsumerIrManager" />
安卓只有在API版本大于等于19的环境下才能使用红外线,可以在Java代码中检测版本:
// 自检,支持红外线就返回true
private boolean haveIr() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
(ConsumerIrManager) getApplicationContext().getSystemService(Context.CONSUMER_IR_SERVICE) != null;
}
以上代码扩展了一下,除了检测版本外,还检测了手机是否包含对应的硬件,如果同时满足要求,就返回true
安卓通过ConsumerIrManager类控制红外线发送,以下代码可以获取设备支持的红外线调制频率:
ConsumerIrManager.CarrierFrequencyRange[] frequencyRanges = IRM.getCarrierFrequencies();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("支持的频率:(单位:赫兹)\n");
if (frequencyRanges.length > 0) {
for (ConsumerIrManager.CarrierFrequencyRange range : frequencyRanges) {
stringBuffer.append(range.getMinFrequency())
.append("~")
.append(range.getMaxFrequency())
.append("\n");
}
}
要发送红外线,首先要获取ConsumerIrManager的实例,以下为我封装的方法,它返回一个ConsumerIrManager对象:
public static ConsumerIrManager getConsumerIrManager(Context context) {
if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return (ConsumerIrManager) context.getSystemService(Context.CONSUMER_IR_SERVICE);
} else {
return null;
}
}
获取ConsumerIrManager实例后,可以调用transmit方法发射红外线:
// 获取ConsumerIrManager实例
ConsumerIrManager consumerIrManager = getConsumerIrManager(context);
// 发射
consumerIrManager.transmit(38000, new int[] {100, 200, 100, 500});
这个方法接收两个参数,第一个参数是一个int型整数,第二个参数为一个int数组,下面解释它们的含义:
参数1:也就是代码中的38000,代表载波频率为38000Hz。
参数2:代表电平持续的时间,代码中new int[] {100, 200, 100, 500}意为先持续发送100毫秒低电平,再发送200毫秒高电平,再发送100毫秒低电平,再发送500毫秒高电平。更长的数组以此类推即可。
3.红外线通讯协议
通过控制高低电平,就可以实现任意的红外线协议。如美的空调使用的红外协议(NEC协议),它的通信格式为:L,A,A’,B,B’,C,C’, S, L,A,A’,B,B’,C,C’。其中,L为引导码,先忽略剩下的部分,看看如何将引导码实现:
引导码的格式为4400ms低电平+4400ms高电平,那么发送引导码的代码可以写为:
consumerIrManager.transmit(38000, new int[] {4400, 4400});
在NEC协议中,使用低高电平的不同组合方式实现01逻辑:
逻辑0:540ms低电平+540ms高电平
逻辑1:540ms低电平+1620ms高电平
而8个0与1的组合可以表示任意0~255之间的整数:
于是,就实现了用红外线传输整数。
限于精力有限,无法完整介绍NEC协议,这里直接附上一个我自己写的美的空调的控制工具类:(里面的MgLog类是我自己的日志打印工具类,实际上就是Log类套了一层皮,与它相关的代码可以直接删除,不影响功能)
package com.example.mdircontroltest01;
import android.content.Context;
import android.hardware.ConsumerIrManager;
import android.os.Build;
/**
* 美的红外线工具类,调用了MyLog工具类
* 通常编码格式为: L,A,A’,B,B’,C,C’, S, L,A,A’,B,B’,C,C’
* 第一帧与第二帧间通过S间隔,两帧内容相同,L为引导码
*/
public class MDInfraredRayUtil {
// 温度常数
public static int TEMPERATURE = 64;
// logt
private static final String TAG = "MyTest";
// 频率
public static final int frequency = 38000;
// 通讯协议常量(NEC协议)
// 引导码
public static final int START_L = 4400;
public static final int START_H = 4400;
// 一次发送终止符
public static final int END_L = 560;
public static final int END_H = 20000;
// 低电平
public static final int LOW = 540;
// 高电平0
public static final int HIGH0 = 540;
// 高电平1
public static final int HIGH1 = 1620;
// 开机数组 AA'BB'CC'
public static int[] OPEN_ARRAY = new int[] {
178, 255-178,
63, 255-63,
TEMPERATURE, 255-TEMPERATURE
};
// 关机数组
public static final int[] CLOSE_ARRAY = new int[] {
178, 77, 123, 132, 224, 31
};
// 关机QQ'YY'
public static final int[] QY = new int[] {
0, 255, 0, 255
};
// 分隔符S
public static final int S_L = 540;
public static final int S_H = 5220;
private MDInfraredRayUtil() {
// 私有化构造方法
}
public static ConsumerIrManager getConsumerIrManager(Context context) {
if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return (ConsumerIrManager) context.getSystemService(Context.CONSUMER_IR_SERVICE);
} else {
return null;
}
}
// 将二进制数组转换为红外线信号数组
public static int[] binaries2Irs(int[] binaries) {
if (null == binaries || binaries.length == 0) {
return null;
}
int[] ans = new int[binaries.length * 2];
int i;
for (i = 0;i < binaries.length;i++) {
switch (binaries[i]) {
case 0:
ans[i * 2] = LOW;
ans[i * 2 + 1] = HIGH0;
break;
case 1:
ans[i * 2] = LOW;
ans[i * 2 + 1] = HIGH1;
break;
default:
MyLog.d(TAG, "二进制数组不合法");
}
}
return ans;
}
/*
// 空调关机(已弃用)
public static void openF(Context context) {
ConsumerIrManager manager = getConsumerIrManager(context);
int[] binaries = new int[] {
1, 0, 1, 1, 0, 0, 1, 0,
0, 1, 0, 0, 1, 1, 0, 1,
0, 1, 1, 1, 1 ,0, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0,
1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1
};
}
*/
// 8位转二进制数组
public static int[] ints2binaries(int[] ints) {
int[] binaries = new int[ints.length * 8];
int i, j, mid;
for (i = 0;i < ints.length;i++) {
mid = ints[i];
for (j = 0;j < 8;j++) {
binaries[i * 8 + j] = (mid >> (7-j)) & 0x1;
}
}
return binaries;
}
// 开机,返回发送的数据
public static int[] openF(Context context) {
return send(context, OPEN_ARRAY, true);
}
// 控制空调状态
public static int[] send(Context context, int[] array, boolean isOpen) {
ConsumerIrManager manager = getConsumerIrManager(context);
// 获取最终发送码
int[] binaries = ints2binaries(array);
int[] irs = binaries2Irs(binaries);
int[] ans;
if (isOpen) {
ans = new int[2 + irs.length + 2 + 2 + irs.length + 2];
} else {
ans = new int[2 + irs.length + 2 + 2 + irs.length + 2 + 2 + irs.length + 2];
}
ans[0] = START_L;
ans[1] = START_H;
int i, j, k;
for (i = 0;i < irs.length;i++) {
ans[2+i] = irs[i];
}
// 分隔码S
ans[irs.length + 2] = S_L;
ans[irs.length + 3] = S_H;
// 重复第二帧
ans[irs.length + 4] = START_L;
ans[irs.length + 5] = START_H;
for (i = 0;i < irs.length;i++) {
ans[irs.length + 6 + i] = irs[i];
}
// 如果是开机控制,则加入结束码即可
if (isOpen) {
// 再加入结束码
ans[2 + irs.length + 2 + 2 + irs.length] = END_L;
ans[2 + irs.length + 2 + 2 + irs.length + 1] = END_H;
} else {
// 加入关机特殊码:S,L,A,A',Q,Q',Y,Y',END
// 直接关机的话,Q与Y的八位全是0
i = 2 + irs.length + 2 + 2 + irs.length;
for (;i < 2 + irs.length + 2 + 2 + irs.length + 2 + 2 + 32; i++) {
ans[i] = ans[i - (2 + 2 + irs.length)];
}
// 获取QQ'YY'的Irs时间长度码数组
int[] QQYY = binaries2Irs(ints2binaries(QY));
for (;i < 2 + irs.length + 2 + 2 + irs.length + 2 + 2 + 32 + QQYY.length;i++) {
ans[i] = QQYY[i - (2 + irs.length + 2 + 2 + irs.length + 2 + 2 + 32)];
}
// 加入结束码
ans[i] = END_L;
i++;
ans[i] = END_H;
}
// 最终发送码获取完成,开始发送
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
manager.transmit(frequency, ans);
}
return ans;
}
// 关机
public static int[] closeF(Context context) {
return send(context, CLOSE_ARRAY, false);
}
// 设置OPEN_ARRAY,返回false代表格式错误
public static boolean setOpenArray(int[] ints) {
if (ints == null) {
return false;
}
if (ints.length != 6) {
return false;
}
int i = 0;
for (;i < 3;i++) {
if (ints[i * 2] < 0 || ints[i * 2 + 1] < 0 || ints[i * 2] + ints[i * 2 + 1] != 255) {
return false;
}
}
OPEN_ARRAY = ints;
return true;
}
public static void arrayInit() {
OPEN_ARRAY = new int[] {
178, 255-178,
63, 255-63,
32, 255-32
};
}
// 改变温度
public static void changeTemperature(int targetTem) {
int answer = 32;
switch (targetTem) {
case 17:
answer = 0;
break;
case 18:
answer = 16;
break;
case 19:
answer = 48;
break;
case 20:
answer = 32;
break;
case 21:
answer = 96;
break;
case 22:
answer = 112;
break;
case 23:
answer = 80;
break;
case 24:
answer = 64;
break;
case 25:
answer = 192;
break;
case 26:
answer = 208;
break;
case 27:
answer = 144;
break;
case 28:
answer = 128;
break;
case 29:
answer = 160;
break;
case 30:
answer = 176;
break;
}
TEMPERATURE = answer;
// 更新
OPEN_ARRAY = new int[] {
178, 255-178,
63, 255-63,
TEMPERATURE, 255-TEMPERATURE
};
}
}
它可以实现简单的开关机功能与温度的调节,再Activity中的具体使用方法为:
// 设置温度(17~30之间),这里设置为25℃
MDInfraredRayUtil.changeTemperature(25);
// 发送红外线指令给空调
MDInfraredRayUtil.openF(getApplicationContext());
经过工具类的封装,两行代码就可以支配空调。