抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

其实这个想法比较简单,开发板连接到校园网,然后用户去 get 开发板的一个特定网址即可。互联网控制比较简单,难点是在如何控制红外发射器。

想要发射一段红外信号,先要了解现在常用的红外编码模式。

NEC 协议

NEC 协议包括引导码、8 位客户码,8 位客户码的补码,8 位按键值,8 位按键值的补码。主要编码方式是 pwm (pulse width modulation)或者 pfm(Pulse frequency modulation)。

从 NEC 协议一次命令的长度来看,NEC 协议编码的数据量不会很多,如果要做一个空调遥控器,那么需要编码温度、模式等等信息,16bits 一定是不够用的。事实上各个空调厂家都有自己的编码方式,编码一个相当长的控制指令。

格力空调的协议

ref:https://blog.csdn.net/qq2874424207/article/details/96475839

格力空调的编码包括:起始码(S)+35位数据码+连接码(C)+32位数据码

其中,各个单位编码都采用 pwm 调制。而且这个命令已经塞得比较紧凑了,比如说把 16 度到 30 度塞进 4 个 bits。

而后面存在 01 交错的校验码,原因是如果发生一定时序错位,01 交错带来的影响是最为明显的。事实上,周围环境的影响对红外线是影响比较大的,为了提高控制准确率,有必要不惜数据冗余做出如此的设计。

当然,虽然知道了格力空调的协议,但是真正编写代码是要死人的,所以我们倾向于用红外接收器接受空调遥控器的信号,然后把信号原封不动地让发射器发出来。

着手研究

一开始我们参考的是 http://ultirobot.com/2018/04/07/ircontroler/,他的代码有一些头文件问题,可能是版本不对,就先不考虑了。

然后寻求 Arduino 自己库文件的支持,我们用的是比较著名的 https://github.com/Arduino-IRremote/Arduino-IRremote 库,这个库对 NEC 等较短协议的支持比较好,但是里面存在 RAW_BUFFER 的限制,显示不全 70 bits 左右的空调指令,最窒息的是开启 DEBUG 会 CE,所以被这个坑了好久之后我才另寻出路。

之后我们找到的是 https://github.com/crankyoldgit/IRremoteESP8266,运行实例库里面的 TurnOnGreeAC.ino(其中 kIrLed=4 就是代表板子 D4 口,不用按照 GPIO 口插线),我们惊奇的发现空调出现反应,但是不显示温度。这时候我们想到可以先用 IRrecvDumpV2.ino 来测试空调遥控器发出的到底是什么样的空调协议。

我们需要把:

1
2
3
4
5
#ifdef ARDUINO_ESP32C3_DEV
const uint16_t kRecvPin = 10; // 14 on a ESP32-C3 causes a boot loop.
#else // ARDUINO_ESP32C3_DEV
const uint16_t kRecvPin = 14;
#endif // ARDUINO_ESP32C3_DEV

下面的 14 改成 4。

经过实验,发现这个根本就不是格力的协议,而是Kelvinator,一家美国电器公司的协议。(又被坑了)

知道这个就好做了,我们利用程序现成的接口:

1
2
3
4
5
6
7
8
9
ac.on();
ac.setFan(1);
ac.setMode(kKelvinatorCool);
ac.setTemp(26);
ac.setSwingVertical(false, kKelvinatorSwingVOff);
ac.setSwingHorizontal(true);
ac.setXFan(true);
ac.setIonFilter(false);
ac.setLight(true);

就可以轻松地完成控制。

最后提一嘴 ESP8266 原生是不支持 post 协议的,必须 get 后面 url 带上若干参数,比如我们这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void handleAC() {
String temperature = server.arg("t");
String on = server.arg("on");
String fan = server.arg("fan"); //风速
String mode = server.arg("mode"); //模式
/*
const uint8_t kGreeAuto = 0;
const uint8_t kGreeCool = 1;
const uint8_t kGreeDry = 2;
const uint8_t kGreeFan = 3;
const uint8_t kGreeHeat = 4;
*/
String light = server.arg("light"); //是否开启显示屏灯光
String token = server.arg("token"); //token=xxxxxx;
if (token != "xxxxxx") {
server.send(403, "text/plain", "glgjssy,qyhfbqz"); //吟诗
return ;
}
if (on == "1") ac.on();
else ac.off();
ac.setFan(fan.toInt());
ac.setMode(mode.toInt());
ac.setTemp(temperature.toInt());
ac.setLight(light.toInt());
ac.send();
printState();
server.send(200, "text/plain", "ok");
}

ac control.ino 总体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ir_Kelvinator.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <iomanip>
#include <iostream>
#include <sstream>

#ifndef STASSID
#define STASSID "你的 WIFI 名称"
#define STAPSK "你的 WIFI 密码"
#endif


const char* ssid = STASSID;
const char* password = STAPSK;

ESP8266WebServer server(80);

void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
const uint16_t kIrLed = 4; // ESP8266 GPIO pin to use. Recommended: 4 (D4).
IRKelvinatorAC ac(kIrLed); // Set the GPIO to be used for sending messages.

void handleAC() {
String temperature = server.arg("t");
String on = server.arg("on");
String fan = server.arg("fan"); //风速
String mode = server.arg("mode"); //模式
/*
根据头文件
const uint8_t kGreeAuto = 0;
const uint8_t kGreeCool = 1;
const uint8_t kGreeDry = 2;
const uint8_t kGreeFan = 3;
const uint8_t kGreeHeat = 4;
*/
String light = server.arg("light"); //是否开启显示屏灯光
String token = server.arg("token"); //token=设置一个你自己的token;
if (token != "设置一个你自己的token") {
server.send(403, "text/plain", "glgjssy,qyhfbqz"); //吟诗
return ;
}
if (on == "1") ac.on();
else ac.off();
ac.setFan(fan.toInt());
ac.setMode(mode.toInt());
ac.setTemp(temperature.toInt());
ac.setLight(light.toInt());
ac.send();
printState();
server.send(200, "text/plain", "ok");
}

void printState() {
// Display the settings.
Serial.println("Kelvinator A/C remote is in the following state:");
Serial.printf(" %s\n", ac.toString().c_str());
// Display the encoded IR sequence.
unsigned char* ir_code = ac.getRaw();
Serial.print("IR Code: 0x");
for (uint8_t i = 0; i < kKelvinatorStateLength; i++)
Serial.printf("%02X", ir_code[i]);
Serial.println();
}

void setupac() {
ac.begin();
}
void setupserver() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);

// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());

if (MDNS.begin("esp8266")) {
Serial.println("MDNS responder started");
}

if (SPIFFS.begin()) { // 启动闪存文件系统
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}

server.enableCORS(true);
server.on("/setac", handleAC);

server.begin();
Serial.println("HTTP server started");
}
void setup() {
setupac(); //进行空调遥控器初始化
setupserver(); //服务器初始化
}

void loop() {
server.handleClient();
}

再加上 html 页面的 body 部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>Rhodonite</title>
</meta>
<meta name="viewport" content="width=device-width, initial-scale=0.5, minimum-scale=1.0, maximum-scale=4.0">
</meta>
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/semantic.min.css">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://semantic-ui.com/dist/semantic.min.js"></script>
<script type="text/javascript" src="https://chartjs.bootcss.com/samples/utils.js"></script>
<script type="text/javascript" src="https://chartjs.bootcss.com/dist/Chart.bundle.js"></script>
</head>

<body>
<script>
server = "http://10.xx.xx.xx"//服务器的ip
//ac settings
ac_token=你自己设的token;
ac_mode=1;//默认制冷
ac_fan=0;
</script>
<div class="ui container">
<div class="ui fluid segment">
<div class="ui top attached label">宿舍空调</div>
<div class="ui five cards">
<div class="card">
<div class="content">
<div class="header">电源</div>
<button class="positive ui icon button" id="ac-power" onclick=switch_ac_power()>
<i class="power off icon"></i>
</button>
</div>
</div>
<div class="card">
<div class="content">
<div class="header">指示灯</div>
<button class="positive ui icon button" id="ac-light-power" onclick=switch_ac_light_power()>
<i class="power off icon"></i>
</button>
</div>
</div>
<div class="card">
<div class="content">
<div class="header">温度</div>
<div class="ui vertical buttons">
<button class="ui icon button" onclick="ac_temperature(+1)">
<i class="angle up icon"></i>
</button>
<button class="ui button" id="ac-temperature">27</button>
<button class="ui icon button" onclick="ac_temperature(-1)">
<i class="angle down icon"></i>
</button>
</div>
</div>
</div>
<div class="card">
<div class="content">
<div class="header">模式</div>
<div class="ui vertical buttons" id="mode-buttons">
<button class="ui button" onclick=set_mode(0)>自动模式</button>
<button class="ui primary button" onclick=set_mode(1)>制冷模式</button>
<button class="ui button" onclick=set_mode(2)>除湿模式</button>
<button class="ui button" onclick=set_mode(3)>送风模式</button>
<button class="ui button" onclick=set_mode(4)>制热模式</button>
</div>
</div>
</div>
<div class="card">
<div class="content">
<div class="header">风速</div>
<div class="ui vertical buttons" id="fan-buttons">
<button class="ui button" onclick=set_fan(0)>自动</button>
<button class="ui button" onclick=set_fan(5)>5</button>
<button class="ui button" onclick=set_fan(4)>4</button>
<button class="ui button" onclick=set_fan(3)>3</button>
<button class="ui button" onclick=set_fan(2)>2</button>
<button class="ui button" onclick=set_fan(1)>1</button>
</div>
</div>
</div>
</div>
<button class="fluid ui labeled icon button" onclick=send_ac_command()>
<i class="paper plane icon"></i>
Biu~
</button>
</div>
</div>
<script>
function switch_ac_power() {
if ($("#ac-power").attr("class") == "positive ui icon button") {
$("#ac-power").attr("class", "negative ui icon button");
}
else {
$("#ac-power").attr("class", "positive ui icon button");
}
}
function switch_ac_light_power() {
if ($("#ac-light-power").attr("class") == "positive ui icon button") {
$("#ac-light-power").attr("class", "negative ui icon button");
}
else {
$("#ac-light-power").attr("class", "positive ui icon button");
}
}
function ac_temperature(delta) {
$("#ac-temperature")[0].innerHTML = (String)((Number)($("#ac-temperature")[0].innerHTML) + delta);
console.log($("#ac-temperature")[0].innerHTML);
}
function set_mode(mode){
ac_mode=mode;
for (var i=0;i<5;i=i+1){
$(`#mode-buttons > button:nth-child(${i+1})`).attr("class",mode==i?"ui primary button":"ui button");
}
}
function set_fan(mode){
ac_fan=mode;
if (mode==0){
for (var i=0;i<7;i=i+1){
$(`#fan-buttons > button:nth-child(${i+1})`).attr("class",mode==i?"ui primary button":"ui button");
}
}
else{
for (var i=0;i<7;i=i+1){
$(`#fan-buttons > button:nth-child(${i+1})`).attr("class",i>6-mode-1?"ui primary button":"ui button");
}
}
}
function send_ac_command() {
ac_temp=(Number)($("#ac-temperature")[0].innerHTML);
ac_on=(Number)($("#ac-power").attr("class") == "positive ui icon button");
ac_light_on=(Number)($("#ac-light-power").attr("class") == "positive ui icon button");
console.log(`/setac?t=${ac_temp}&on=${ac_on}&fan=${ac_fan}&mode=${ac_mode}&light=${ac_light_on}&token=${ac_token}`);
$.get(server + `/setac?t=${ac_temp}&on=${ac_on}&fan=${ac_fan}&mode=${ac_mode}&light=${ac_light_on}&token=${ac_token}`,
function(res){
console.log(res);
}
);
}
</script>
</body>

ui 如图:

目前写了一个最新的记忆化的代码,还没放上去www. 有一个想法,我们再加上蓝牙模块,连接小米温湿度计2,就可以搞一个更加恒温的空调(bushi)

评论