友情提示:380元/半年,儿童学编程,就上码丁实验室。
我是潘,曾经是个工程师。这是“Arduino 公开课” 系列的入门教程。上一节课,我们已成功让 Arduino 连接到网络,现在要让 Arduino 做的第一件事情:访问网络,并读取服务器上的信息。有任何疑问请在评论区提出,我会逐一回答。
IoT 中,指令一般由中枢(服务器)向节点发送,但在一些特殊环境中,比如,节点没有固定的IP地址(尤其在内陆,DDNS 动态域名服务非常不稳定,从互联网访问家庭宽带IP很难实现)。换种思维,让 Arduino 主动去抓取服务器上的指令,或者其他信息?这很容易实现,与家里的电脑访问web一样,让 Arduino 访问服务器即可。
下面是访问 ardui.co 的完整程序:
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
|
/*
作者:Ardui.Co
效果:抓取网站内容
版本:1.0
更新时间:2017年6月13日
*/
#include <EtherCard.h>
#include <avr/pgmspace.h> //AVR 库,包含了“虚拟内存”的功能
staticbytemymac[]={0×74,0×69,0×69,0x2D,0×30,0×31};
constcharwebsite[]PROGMEM={“ardui.co”};// 将网站地址变量存储在 FLASH 中,节省内存,可理解为虚拟内存
byteEthernet::buffer[700];//设置缓冲区大小
staticuint32_t timer;
staticvoidresponse_callback(bytestatus,wordoff,wordlen){
Serial.print((constchar*)Ethernet::buffer+off+265);//过滤头信息
}
voidsetup(){
Serial.begin(57600);
Serial.println(“Client Demo”);
Serial.println();
if(!ether.begin(sizeof Ethernet::buffer,mymac,10))
Serial.println(“Failed to access Ethernet controller”);
else
Serial.println(“Ethernet controller initialized”);
Serial.println();
if(!ether.dhcpSetup())//自动获取 IP、DNS 服务器等信息
Serial.println(“Failed to get configuration from DHCP”);
else
Serial.println(“DHCP configuration done”);
ether.printIp(“IP Address:\t”,ether.myip);
ether.printIp(“Netmask:\t”,ether.netmask);
ether.printIp(“Gateway:\t”,ether.gwip);
Serial.println();
if(!ether.dnsLookup(website))//检查DNS信息
Serial.println(“DNS failed”);
else
Serial.println(“DNS resolution done”);
ether.printIp(“SRV IP:\t”,ether.hisip);
Serial.println();
}
voidloop(){
ether.packetLoop(ether.packetReceive());
if(millis()>timer){
timer=millis()+5000;//延时5s
ether.browseUrl(PSTR(“/archives/”),“1″,website,response_callback);
}// PSTR()将固定地址写入FLASH 而非SRAM,节省内存
}
|
我们要用到 IDE 内置的 AVR库(后面的课程介绍) “pgmspace.h”,以启用”虚拟内存“功能,节省SRAM。变量 website[],用到 ”PROGMEM“,作用就是将变量信息存储在 FLASH,使用时再调用。FLASH 速度比 SRAM 慢,此方法会影响运行速度。
不过,谁叫 UNO 只有 2KB SRAM 呢?在资源消耗极高的程序中,必须精打细算:》如果是 Arduino Mege 2560,SRAM 容量大很多,且可以扩展,但是后话了。
第二个要点是,确认目标服务器的地址,比如 “ ardui.co ”。这个地址通过 DNS 服务器转换为真实的 IP。上节课使用了静态设置 staticSetup() 或者 dhcpSetup() 两种方式来设置 Arduino 的 IP、DNS 等信息,现在通过 dnsLookup() 方法来检查 DNS 服务器是否正常:
1
2
3
4
5
6
7
8
9
|
constcharwebsite[]PROGMEM={“ardui.co”};
……
if(!ether.dnsLookup(website))
Serial.println(“DNS failed”);
else
Serial.println(“DNS resolution done”);
ether.printIp(“SRV IP:\t”,ether.hisip);
Serial.println();
}
|
确认连接到 DNS 服务器后,就可以开始 Web 之旅了。
EtherCard 提供了一个便捷的方法 browseUrl() 访问服务器,其数据结构如下:
1
2
3
4
5
6
|
voidEtherCard::browseUrl(
constchar*urlbuf,
constchar*urlbuf_varpart,
constchar*hoststr,
void(*)(uint8_t,uint16_t,uint16_t)callback
)
|
看起来很复杂,第1个、第2个形参(urlbuf、urlbuf_varpart)分别是地址固定部分,后者变量;第3个形参 hoststr 很好理解,即网址;第4个形参 callback,为回调函数名称,当连接结束时调用的函数。举个例子:
1
2
3
|
charwebsite[]PROGMEM=“ardui.co”;
……
ether.browseUrl(PSTR(“/demo/”),“test.php”,website,response_callback);
|
PSTR() 属于AVR 编程的知识,简单理解为,将地址固定的部分存储在 FLASH 中,以达到节省 Arduino 内存(SRAM)的目的。“test.php” 为我们要访问的文件,可以是html,也可以是txt,只要编码一致,后缀不限。“website” 即我们的网址 “ardui.co” 。难点在于其中的回调函数 :“response_callback” 。
先复习一下,上节课用到的缓冲区声明:
1
|
byteEthernet::buffer[700];
|
该声明的作用为设置缓冲区的大小,这个缓冲区用于储存外来数据包,当调用 browseUrl() 方法后,服务器反馈的数据包都在里面。这里设计的回调函数 response_callback() 作用是读取缓冲区的信息:
1
2
3
|
staticvoidresponse_callback(bytestatus){
Serial.print(Ethernet::buffer+off);
}
|
通过这个函数,串口会打印出缓冲区里面所有的信息:
红色框框是头文件信息,网站的服务器、缓存控制、编码等基本信息会呈现在这里,紧接着才是网页的内容。头文件信息没有太大用处,需要过滤掉。这里不需要特别的方法或者函数。利用 C 语言的指针,指向头文件结束的位置即可:
1
2
3
|
staticvoidresponse_callback(bytestatus,wordoff,wordlen){
Serial.print((constchar*)Ethernet::buffer+off+265);
}
|
数值 “265” 是头文件的大小,将头文件拷贝到文本编辑器,就可以查到:
1
|
(constchar*)
|
这是一个指针,指向缓存开始加上265个byte的位置。
所有知识难点都已经介绍完了,要注意的是:
1
|
ether.packetLoop(ether.packetReceive());
|
响应函数必须保留,否则程序是不工作的。另外,millis() > timer 的设计是为了延时,如果不需要频繁抓取,可以增加 timer。
上面并非整个网页的内容,而是缓冲区(Ethernet::buffer)所有的信息,一开始设置为 700 bytes,(尝试设置为1000 bytes 呢?)。另外,由于 Arduino 与中文网站的编码不一致,因此显示乱码。