如何用Arduino做一个简单的Web服务器

这个教程将会教会你怎么用Arduino搭建一个简单的Web服务器,包含了设置和运行的所有细节,让我们开始吧。

开始之前,你需要了解一些基本的HTML,不用担心,这非常简单,如果你从来没有做过HTML,可以去w3schools上看看教程。

这个服务器不会做太花哨的事情,但是想要显示连接到引脚的设备的数据或者想要从网页上与电路交互的人来说,它非常有用。如果这不能满足你的需求,可以尝试Raspberry Pi网络服务器。

设备清单:

  • Arduino Uno
  • 网线接口
  • 网线
  • SD 卡(可选)

基础代码:

如果你是编码新手,或者对一些基础知识感兴趣,请继续阅读,否则你可以跳过这一节,然后到设置Web服务器与SD卡部分。

初始化:

最开始需要初始化服务器。

首先,定义一个mac地址,下面这个地址在大多数家庭网络中应该是可以的。如果你知道自己到底要的是什么,请随意更改。

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

其次,定义一个IP地址,这可以通过简单地调用IPAddress类来完成。确保IP地址不会冲突。

IPAddress ip(192, 168, 1, 177);

第三,设置服务器监听的端口号。默认情况下,普通的HTTP运行在80端口上。如果你了默认值,那么当你在浏览器中访问网页时,你将需要使用定义端口。例如,如果你的端口是14213,那么地址就会是这样的,192.168.1.177:14213

EthernetServer server(80);

最后,在setup函数中,初始化以太网设备。一旦初始化完成,只需调用server.begin(),现在应该能够监听连接,并在被访问时做出响应。

Ethernet.begin(mac, ip);
server.begin();

检测新连接

在循环函数中,需要在每次循环时检查是否有新的连接。如果检测到新的连接,就执行网页逻辑代码。

下面的代码将检测新的客户端何时试图连接到服务器。

EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
        if (client.available()) {

一旦有连接进来,我们就返回一段HTML。

返回HTTP响应头

浏览器遵循HTTP协议,为了让浏览器正确显示网页,我们需要用HTML响应头来回复。

如果你想了解更多关于头字段的信息,那么这个非常方便的wiki页面描述的很详细。

client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();

输出HTML

输出HTML可以通过调用client.println()或client.print()来完成,并将文本作为参数。

下面是一个输出一些HTML的例子。

client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.print("<h1>Analogue Values</h1>");

另一种选择是将HTML文件存储在SD卡上,你可以从SD卡上访问和加载。然后您可以执行AJAX请求来更新网页上的元素。

你也可以这样做来让Arduino执行一些动作,比如打开和关闭LED。

关闭连接

当处理完网页后,应该延迟一小段时间,以确保客户端得到数据。一秒左右的时间后,直接关闭与客户端的连接。

delay(1);
client.stop();

没有SD卡的Arduino网络服务器

如果你没有使用SD卡,让网络服务器运行就更简单了。

需要注意的是,如果您确实插入了SD卡,但没有使用,那么可能会让sketch与Arduino产生通讯问题。为了避免这种情况发生,请在设置函数中添加以下两行。

pinMode(4, OUTPUT);
digitalWrite(4, HIGH);

添加他们后,你可以正常使用SD卡了。

要快速让Arduino网络服务器启动并运行,只需打开sketch并复制并粘贴下面的代码。

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 1, 177);

EthernetServer server(80);

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}

void loop() {
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
        if (c == '\n' && currentLineIsBlank) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println("Refresh: 5");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.print("<h1>Analogue Values</h1>");
          for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
            int sensorReading = analogRead(analogChannel);
            client.print("analog input ");
            client.print(analogChannel);
            client.print(" is ");
            client.print(sensorReading);
            client.println("<br />");
          }
          client.println("</html>");
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
        } else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

把这段代码上传到Arduino后,你就有了一个非常基本的网络服务器。

它每5秒刷新一次Arduino的模拟引脚的值,这会儿只是随机值,除非他们连接了一个实际的设备或传感器。

你也可以监控串口监控器的数据。

带SD卡的Arduino网络服务器

如果你决定走SD卡路线的Arduino网络服务器,HTML文件需要在你的电脑上创建,然后复制到SD卡上。

从SD卡加载网页的一个好处是,你可以有更复杂的页面,而不需要写数百个client.write。 它还有助于减少内存占用。

初始化SD卡

在访问SD卡之前,需要导入SD包,并在设置函数中初始化它。设置函数中的check 会查看是否可以访问卡,否则会在串口监视器中抛出一个错误。

Serial.println("Checking SD card is accessible...");
if (!SD.begin(4)) {
   Serial.println("ERROR - SD card initialization failed!");
   return;    // init failed
}
Serial.println("SUCCESS - SD card initialized.");

加载文件

只需使用 sd.open(filename) 打开文件。使用if语句确保文件存在,然后循环浏览文件的内容,直到到达文件末尾。

webPage = SD.open("index.htm"); // open web page file
if (webPage) {
    while (webPage.available()) {
    client.write(webPage.read()); // send web page to client
}
webPage.close();

下面是一个通过SD卡加载网页的例子

通过AJAX执行操作

有时候你会想通过网页控制一个设备或传感器,在本节中,我们看看如何使用AJAX执行请求和更新,页面不会整个被更新,只需更新需要更新的部分。

AJAX或异步JavaScript和XML有点太复杂,无法在本教程中完全涵盖。

AJAX例子

AJAX的工作原理是向服务器发送一个请求,然后服务器会检查头中是否存在字符串ajaxrefresh或ledstatus。如果存在,服务器生成相关数据并返回。

然后JavaScript会找到相关的具有正确id的HTML元素,然后用AJAX请求的响应替换innerHTML。

下面的例子是通过GET变量ajaxrefresh向Arduino上运行的Web服务器发送请求。如果服务器返回数据,它将替换具有id analogue_data的元素的innerHTML。

这个脚本每5秒运行一次。

<script>window.setInterval(function(){
  nocache = "&nocache=" + Math.random() * 10;
  var request = new XMLHttpRequest();
  request.onreadystatechange = function() {
    if (this.readyState == 4) {
      if (this.status == 200) {
        if (this.responseText != null) {
          document.getElementById("analoge_data").innerHTML = this.responseText;
        }
      }
    }
  }
  request.open("GET", "ajaxrefresh" + nocache, true);
  request.send(null);
  }, 5000);
</script>

Arduino Ajax函数

每当通过AJAX请求服务器时,下面的两个函数就会被调用,它们非常简单,第一个函数读取模拟引脚并返回相关数据。

第二个函数将根据当前状态打开或关闭RED LED。它还会返回LED的状态,以便AJAX能够更新网页上的值。

void ajaxRequest(EthernetClient client)
{
  for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
    int sensorReading = analogRead(analogChannel);
    client.print("analog input ");
    client.print(analogChannel);
    client.print(" is ");
    client.print(sensorReading);
    client.println("<br />");
  }
}

void ledChangeStatus(EthernetClient client)
{
  int state = digitalRead(RED);
  Serial.println(state);
  if (state == 1) {
    digitalWrite(RED, LOW);
    client.print("OFF");
  }
  else {
    digitalWrite(RED, HIGH);
    client.print("ON");
  }
}

Ajax返回HTML

下面的例子有所有的HTML和JavaScript完全由Arduino生成。JavaScript向Arduino服务器发出AJAX请求,服务器根据它提供的结果更新页面。

通过在Arduino上生成所有代码,Arduino会给出运行空间不足的警告,如果你想在页面上有大量的传感器,最好把HTML存放在SD卡上。

void loop() {
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if( HTTP_req.length() < 120) 
           HTTP_req += c; // save the HTTP request 1 char at a time 
           Serial.write(c); 
           if (c == '\n' && currentLineIsBlank)
           { 
            // send a standard http response header
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println("Connection: close");
            client.println();
            Serial.println(HTTP_req);
            if (HTTP_req.indexOf("ajaxrefresh") >= 0 ) {
            // read switch state and analog input
            ajaxRequest(client);
            break;
          }
          else if (HTTP_req.indexOf("ledstatus") >= 0 ) {
            // read switch state and analog input
            ledChangeStatus(client);
            break;
          }
          else {
            client.println("<!DOCTYPE HTML>");
            client.println("<html lang=\"en\">");
            client.println("<script>window.setInterval(function(){");
            client.println("nocache = \"&nocache=\" + Math.random() * 10;");
            client.println("var request = new XMLHttpRequest();");
            client.println("request.onreadystatechange = function() {");
            client.println("if (this.readyState == 4) {");
            client.println("if (this.status == 200) {");
            client.println("if (this.responseText != null) {");
            client.println("document.getElementById(\"analoge_data\").innerHTML = this.responseText;");
            client.println("}}}}");
            client.println("request.open(\"GET\", \"ajaxrefresh\" + nocache, true);");
            client.println("request.send(null);");
            client.println("}, 5000);");
            client.println("function changeLEDStatus() {");
            client.println("nocache = \"&nocache=\" + Math.random() * 10;");
            client.println("var request = new XMLHttpRequest();");
            client.println("request.onreadystatechange = function() {");
            client.println("if (this.readyState == 4) {");
            client.println("if (this.status == 200) {");
            client.println("if (this.responseText != null) {");
            client.println("document.getElementById(\"led_status\").innerHTML = this.responseText;");
            client.println("}}}}");
            client.println("request.open(\"GET\", \"?ledstatus=1\" + nocache, true);");
            client.println("request.send(null);");
            client.println("}");
            client.println("</script></head>");
            // output the value of each analog input pin
            client.print("<h1>Analogue Values</h1>");
            client.println("<div id=\"analoge_data\">Arduino analog input values loading.....</div>");
            client.println("<h1>Arduino LED Status</h1>");
            client.println("<div><span id=\"led_status\">");
            if(digitalRead(RED) == 1)
             client.println("On");
            else
              client.println("Off");
            client.println("</span> | <button onclick=\"changeLEDStatus()\">Change Status</button> </div>");
            client.println("</html>");
            break;
          }
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
    HTTP_req = "";
    Serial.println("client disconnected");
  }
}

Ajax返回SD卡上的HTML

通过把HTML存储在SD卡上,代码会干净很多。

void loop()
{
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if ( HTTP_req.length() < 80)
          HTTP_req += c;
        if (c == '\n' && currentLineIsBlank) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          if (HTTP_req.indexOf("ajaxrefresh") >= 0 ) {
            ajaxRequest(client);
            break;
          }
          else if (HTTP_req.indexOf("ledstatus") >= 0 ) {
            ledChangeStatus(client);
            break;
          }
          else {
            webPage = SD.open("index.htm");
            if (webPage) {
              while (webPage.available()) {
                client.write(webPage.read());
              }
              webPage.close();
            }
            break;
          }
          if (c == '\n') {
            currentLineIsBlank = true;
          } else if (c != '\r') {
            currentLineIsBlank = false;
          }
        }
      }
    }
    delay(1);
    client.stop();
    HTTP_req = "";
    Serial.println("client disconnected");
  }
}

疑难解答

这里可能会遇到一些问题,列举如下

  • 无法访问网页
    仔细检查IP地址,确保它没有和网络上的其他设备冲突。另外,试着ping一下IP地址,看看是否有回应。
  • SD卡无法访问
    按住 “关闭 “键,然后插入SD卡,我发现这通常能解决这个问题。
  • CSS
    你可以在网页中加入CSS,也可以在页眉的样式标签之间加入。除非你想让你的网页看起来很好看,否则没有必要加CSS。

更进一步的实现

Web服务器可以实现很多有趣的东西,我列举部分思路在下面

  • 风扇/温度控制
    监控服务器机房、温室或类似的对热和气流很敏感的地方。您可以添加控制装置来打开和关闭加热器和风扇。
  • 计数器
    使用运动传感器来检测运动,并在每次传感器被触发时进行计数。你可以将同样的逻辑应用于几乎所有种类的传感器。

希望这个教程能让你顺利搭建起自己的web服务器,有什么疑问欢迎留言讨论。

 

 

 

 

平均: 5 / 5. votes: 1

到目前为止还没有投票!成为第一位评论此文章。

欢迎转载,请留下出处链接:Labno3 » 如何用Arduino做一个简单的Web服务器

赞 (3)

评论

9+4=