带以太网 Shield 的简单 Arduino Web 服务器






4.33/5 (8投票s)
为带有 Wiznet 5100 以太网 Shield 的 Arduino 实现基本的 HTTP 请求处理。
引言
这是一个用于 Arduino 和 Wiznet 5100 以太网盾的极其简单的 Web 服务器。加载此草图后,您将立即开始接收请求!
- 支持 IPv4。本示例使用 TCP/IP。
- 在端口 80 上监听,这是未经加密的 HTTP 流量的标准 TCP 端口。设备将在 http://192.168.1.23/ 响应。
- 默认网关默认禁用。这意味着设备仅响应来自本地/私有网络的请求。
- 如果出于某种原因需要它响应 Internet 请求,您可以配置路由器进行端口映射,并将默认网关设置为 192.168.1.1。然后设备将通过路由器响应。有关更多信息,请搜索“端口地址转换”。
- 客户端请求和响应代码以 115200 波特率写入控制台。
- 此 Web 服务器仅适用于纯文本,因此任何图像都必须托管在真正的 Web 服务器上(或使用更强大的 Arduino)。
- 请保持现实的期望。如果您的项目超出了基本自动化要求,请考虑完整的嵌入式系统或服务器操作系统。
作者:Mark Scammacca
你需要的东西
(总成本约 10 美元)
- Arduino Uno - 随处可见。在 eBay 上花几美元就能买到一个。
重要提示:选择带有微型 USB 连接器的型号,这样 Wiznet 5100 以太网盾才能轻松安装在其上方。 -
Arduino Wiznet 5100 以太网盾 - eBay 上也有很多。
可选:选择支持 POE(以太网供电)的型号。POE 板必须单独购买并焊接,然后您的 POE 交换机就可以直接通过以太网线为所有 Arduino 设备供电。
重要提示:还有其他类型的以太网盾,此代码仅适用于 Wiznet 兼容型号。但对于任何以太网盾,基本原理都可以通过少量修改后工作。
- Arduino 开发软件(免费) - 完全免费安装。此项目除了开发环境已有的内容外,不需要任何其他依赖项。不需要芯片编程器 - 编程通过 USB 完成。安装 Arduino 软件后,USB 端口还充当串行端口,因此您可以使用 Putty 或 Arduino 软件内置的串行控制台监控此代码的控制台输出。
以太网供电
Wiznet 5100 盾牌有支持以太网供电的版本,但这需要一个单独购买的附加板。有些型号有焊接点可以安装 POE 附加板,有些则没有。请仔细检查。我看到它们被列为“POE 以太网盾”,但它们的意思是“支持 POE,单独出售”。然后该设备可以由 POE 交换机或 POE 注入器供电。
如果您对 POE 感兴趣并想选择合适的交换机类型,有三种主要的标准需要了解
- IEEE 802.3af-2003
- IEEE 802.3at-2009(功率更大,应向后兼容 af 设备)
- 专有 - 通常最便宜,有时与 802.3af 或 802.3at 设备不兼容,会导致不可预测的结果。有时包含匹配的终端。
还可以考虑 POE 分割器。这些分割器通过提供 barrel plug 输出,简化了将非 POE 设备连接到 POE 交换机。POE 分割器从线路中提取电源,并通过 barrel plug 提供给设备。它们接受 48V POE 电源并将其降低到 5VDC、12VDC 等。我用它们的效果很好。
安全注意事项
此代码的安全性未经审查 - 因此直接将其连接到 Internet 可能是一个糟糕的主意。出于此原因,默认网关设置为 0.0.0.0。如果您选择将其连接到路由器,请将默认网关设置为 192.168.1.1(或您的路由器的 IP 地址)并搜索“端口地址转换”。
在仔细保护设备免受黑客攻击之前,请勿在实际产品中使用此代码。一旦人们认为攻击物联网设备有价值,他们普遍会攻击物联网设备。
Using the Code
安装 Arduino 软件后,只需连接 USB 并将此草图加载到您的 Arduino 设备中。设备和以太网盾可以通过 USB 供电。
请求在 void respond()
函数中处理。
默认页面只是一个带有点击计数器的启动页面:http://192.168.1.23/
测试页面显示了如何传入参数并将它们写回响应:http://192.168.1.23/test.html
每次请求都必须完整处理,然后 Arduino 才能响应下一个请求,因此请保持请求简短。
(只需突出显示代码并复制/粘贴。请勿使用“复制代码”按钮,否则粘贴时 HTML 元素将损坏。)
/*
Board: Duemilanove or Diecimila
Processor: ATmega328
Programmer: AVR-ISP mkII
UNO 5V BOARD
and Wiznet 5100 Ethernet Shield
X0 - RX - FTDI
X1 - TX - FTDI
2 DIG02 -
3 DIG03 -
4 DIG04 - SD CARD / RESERVED
5 TIMR1 -
6 DIG06 -
7 DIG07 -
8 DIG08 -
9 DIG09 -
10 DIG10 - SPI ETHERNET SS / RESERVED
11 DIG11 - SPI ETHERNET MOSI / RESERVED
12 DIG12 - SPI ETHERNET MISO / RESERVED
13 DIG13 - SPI ETHERNET SCK / RESERVED (LED FLICKERS DURING SPI COMM)
22 ADC00 -
23 ADC01 -
24 ADC02 -
25 ADC03 -
26 ADC04 -
27 ADC05 -
18? ADC06 -
21? ADC07 -
*/
#include <SPI.h>
#include <Ethernet.h>
/*
MACRO for string handling from PROGMEM
https://todbot.com/blog/2008/06/19/how-to-do-big-strings-in-arduino/
max 149 chars at once ...
*/
char p_buffer[150];
#define P(str) (strcpy_P(p_buffer, PSTR(str)), p_buffer)
// Ethernet Interface Settings
byte mac[] = { 0xAD, 0xDE, 0xEF, 0xBB, 0xFD, 0xDD };
IPAddress ip (192, 168, 1, 23); // Private static IP address
IPAddress myDns (192, 168, 1, 1); // DNS is not needed for this example
IPAddress gateway (0, 0, 0, 0); // Default gateway disabled for security reasons
IPAddress subnet (255, 255, 255, 0); // Class C subnet; typical
// HTTP lives on TCP port 80
EthernetServer server(80);
void setup()
{
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
Serial.begin(115200);
}
// string buffers for receiving URL and arguments
char bufferUrl[256];
char bufferArgs[512];
int urlChars = 0;
int argChars = 0;
// number of characters read on the current line
int lineChars = 0;
// total # requests serviced
long requests = 0;
// connection state while receiving a request
int state = 0;
/*
Typical request: GET /<request goes here>?firstArg=1&anotherArg=2 HTTP/1.1
State 0 - connection opened
State 1 - receiving URL
State 2 - receiving Arguments
State 3 - arguments and/or URL finished
State 4 - client has ended request, waiting for server to respond
State 5 - server has responded
Example of what the server receives:
GET /test.html HTTP/1.1
Host: 192.168.1.23
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
*/
void loop()
{
// listen for incoming clients
EthernetClient client = server.available();
if (client)
{
state = 0;
urlChars = 0;
argChars = 0;
lineChars = 0;
bufferUrl[0] = 0;
bufferArgs[0] = 0;
while (client.connected())
{
if (client.available())
{
// read and echo data received from the client
char c = client.read();
Serial.print(c);
// ignore \r carriage returns, we only care about \n new lines
if (c == '\r')
continue;
// control what happens to the buffer:
if (state == 0 && c == '/')
{
// Begin receiving URL
state = 1;
}
else if (state == 1 && c == '?')
{
// Begin receiving args
state = 2;
}
else if ((state == 1 || state == 2) && c == ' ')
{
// Received full request URL and/or args
state = 3;
}
else if (state == 1 && urlChars < 255)
{
// Receiving URL (allow up to 255 chars + null terminator)
bufferUrl[urlChars++] = c;
bufferUrl[urlChars] = 0;
}
else if (state == 2 && argChars < 511)
{
// Receiving Args (allow up to 511 chars + null terminator)
bufferArgs[argChars++] = c;
bufferArgs[argChars] = 0;
}
else if (state == 3 && c == '\n' && lineChars == 0)
{
// Received a line with no characters;
// this means the client has ended their request
state = 4;
}
// record how many characters on the line so far:
if (c == '\n')
lineChars = 0;
else
lineChars++;
// OK to respond
if(state == 4)
{
// Response given
state = 5;
// increment internally for fun purposes
requests++;
Serial.print(P("Request # "));
Serial.print(requests);
Serial.print(P(": "));
Serial.println(bufferUrl);
// handle the response
respond(client);
// exit the loop
break;
}
}
}
// flush and close the connection:
client.flush();
client.stop();
}
}
void respond(EthernetClient client)
{
if (strcmp(bufferUrl, P("")) == 0)
{
// Requested: / (DEFAULT PAGE)
// send response header
sendHttpResponseOk(client);
// send html page
// max length: ------------------------------------------------------ (149 chars)
client.println(P("<HTML><head><title>Welcome</title></head><body><h1>Welcome, visitor"));
client.print(requests);
client.println(P("!</h1>Click here to visit the <a href=/test.html>Test Page</a><p>"));
client.println(P("String output is stored in progmem to conserve RAM.
That's what all the P( ) stuff is about. Buffer is big enough for 149 chars at once. "));
client.println(P("Be careful not to exceed!<p><font color=red>
This web server is not secured for public access. Use at your own risk.</font> "));
client.println(P("If you want to use this in an actual product,
at least leave the gateway IP setting disabled.
You should consider a design where the Arduino acts"));
client.println(P("as the client, or maybe a design where the Arduino
can only be contacted by a more fully-secured server.
<p>Requests are echoed to the console"));
client.println(P("with baud rate of 115200. Have fun!<p>
<img src='https://cdn.meme.am/instances/250x250/54595677.jpg'/></body></html>"));
}
else if (strcmp(bufferUrl, P("test.html")) == 0)
{
// Requested: test.html
// send response header
sendHttpResponseOk(client);
// send html page
client.println(P("<HTML><head><title>Test Page</title>
</head><body><h1>Test Page</h1>"));
client.println(P("<br><b>Resource:</b> "));
client.println(bufferUrl);
client.println(P("<br><b>Arguments:</b> "));
client.println(bufferArgs);
// max length: ------------------------------------------------ (149 chars)
client.println(P("<br><br><form action='/test.html?'
method='GET'>Test arguments: <input type=text name='arg1'/>
<input type=submit value='GET'/></form>"));
client.println(P("</body></html>"));
}
else
{
// All other requests - 404 not found
// send 404 not found header (oops)
sendHttp404(client);
client.println(P("<HTML><head><title>Resource not found
</title></head><body><h1>The requested resource was not found</h1>"));
client.println(P("<br><b>Resource:</b> "));
client.println(bufferUrl);
client.println(P("<br><b>Arguments:</b> "));
client.println(bufferArgs);
client.println(P("</body></html>"));
}
}
// 200 OK means the resource was located on the server and the browser
// (or service consumer) should expect a happy response
void sendHttpResponseOk(EthernetClient client)
{
Serial.println(P("200 OK"));
Serial.println();
// send a standard http response header
client.println(P("HTTP/1.1 200 OK"));
client.println(P("Content-Type: text/html"));
client.println(P("Connnection: close")); // do not reuse connection
client.println();
}
// 404 means it ain't here. quit asking.
void sendHttp404(EthernetClient client)
{
Serial.println(P("404 Not Found"));
Serial.println();
client.println(P("HTTP/1.1 404 Not Found"));
client.println(P("Content-Type: text/html"));
client.println(P("Connnection: close")); // do not reuse connection
client.println();
}
示例输出
就是这样!
您可以读取或操作数字 IO 引脚,或者执行任何您通常会执行的操作。
内存使用说明
由于字符串会占用宝贵的 RAM 空间,我引用了一个宏,该宏将它们存储在 PROGMEM 中并从中检索。这使您能够为如此微小的处理器创建相当大的网页(真丢人!),并将它们存储在充足的闪存中,而不会牺牲所有 RAM。
权衡是您必须记住将每行文本限制在 149 个字符以内,否则读取 PROGMEM 中字符串的缓冲区将溢出,导致您的代码出现奇怪的错误。
如果您需要更多内存,请考虑 ATmega 或更强大的处理器,或 SD 卡(有其他适用于 Arduino 的 HTTP 实现使用此功能)。
历史
- 2017 年 5 月 20 日:初始版本