这次来做一个基于网页界面的ESP8266的温湿度天气站,以前也发过关于使用关于基于blynk实现的气象采集仪项目,可是对一般用户来说Blynk不太方便,原因是现在blynk的app需要使用谷歌套件,我查证了一下,现在blynk已经不可以单纯的把APK文件安装在安卓手机上,要求手机必须有谷歌套件,否则一打开就会闪退。除非是IOS系统,不然的话会无法启动,对于大部分在国内使用的朋友来说,不FQ安装谷歌套件的话是没法使用的。鉴于这种情况,且国内欠缺类似blynk这样的物联网免费平台,所以这里做一个通用性比较强的项目,只要能有WiFi就可以使用,但是综合起来没有blynk那么强大和方便,以后如果有好的替代方法我会记录下来。
        由于本项目是从最简单的方式说起,项目分为三个,本位以后会陆续发布,将按顺序分为以下三个文章讲解:

  1. 基于简单Web界面显示温湿度和大气压,只能内网使用。(即本编)
  2. 基于Linux已经搭建了LAMP的服务器,通过PHP+MySQL的方式实现数据传输,并实现公网访问,显示方式为图形指标。
  3. 基于第2扁文章,做成一个走势图标形式的显示方式。
            以上三种方式,可以按自己的需求任选一种,不需要三种都做一遍。例如:我只需要简单在内网网页查询温湿度的,那么可以只看第一编文章。例如:我需要公网随时随地查询数据的,则可以看看第二或者第三篇,只需要按自己喜欢的方式选一个显示方式即可。当然,你也可以自己举一反三的把图形和走势图标整合到同一个页面上。

准备环境

  • 硬件准备: 名称 数量 单位 备注
    ESP8266或者ESP32开发板 1
    BEM280传感器 1 市面有3.3V和5V版本,这里使用的是5V
    杜邦线 若干
    面包板 1 随意形状
    3D打印的外壳 1 非必要,这里给一个参考外壳,需要的点此下载
  • 软件环境准备:

这里只是列出我本人当时成功编译出的项目,仅供参考,如果出现编译不成功的,可以试试换成我这里的版本。在开始下一步前,先安装好程序库。

  1. ArduinoIDE : 1.8.12 (推荐新手或者不想台折腾的最好用这个IDE,我这里使用的图片来源是使用vscode)
  2. 程序库:
    1. Adafruit Sensor library : 1.0.2
    2. Adafruit_BME280 library : 2.1.2
  3. 开发板库:
    1. ESP32 : 1.0.4
    2. ESP8266 : 2.7.4

硬件接线

接线都比较简单,只有四根线这里就不另外详细画图了,电压按你自己所买到的为准,看不懂的可以去百度一下ESP8266/32 的pinout图.BEM280是有SPI接口版本的,使用SPI接口也是可以,不过我对速度没要求,加上懒得接线太多所以用了I2C接口。这里是使用 5V 的BEM280,请注意,参考下表:

ESP8266 5V GND D2 D1
BME280 5V GND SDA SCL
ESP32 5V GND GPIO21 GPIO22
BME280 5V GND SDA SCL


接线后套上外壳的外观(点击图片放大)

代码分析

        这里我就不做中文了,接下来两篇文章的网页将会是中文版的.如果显示不全,请点击代码框左上角3个点的图标.

// 加载必要的程序库
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme; // I2C

// 填入你的WiFi ssid 和密码
const char* ssid     = "你的 WiFi SSID";
const char* password = "你的 WiFi 密码";

// 这里默认端口设置为:80
WiFiServer server(80);

// 用于存储HTTP请求的变量
String header;

void setup() {
  Serial.begin(115200);
  bool status;

  // 默认设置
  // (您还可以传入Wire库对象,例如&Wire2)
  //status = bme.begin();  
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  // 使用SSID和密码连接到Wi-Fi网络
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // 输出显示本地IP地址和启动web服务器
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop(){
  WiFiClient client = server.available();   // 监听即将到来的客户端

  if (client) {                             //如果有一个客户端连接
    Serial.println("New Client.");          // 将从串口输出一个信息
    String currentLine = "";                // 制作一个字符串以容纳来自客户端的传入数据
    while (client.connected()) {            // 当客户端连接时循环
      if (client.available()) {             // 如果有要从客户端读取的字节,
        char c = client.read();             // 读一个字节,然后
        Serial.write(c);                    // 将其输出到串口
        header += c;
        if (c == '\n') {                    // 如果字节是换行符
          // 如果当前行为空白,则将连续获得两个换行符。
          // 客户端HTTP请求的结尾,因此发送响应:
          if (currentLine.length() == 0) {
            // HTTP标头始终以响应代码开头(例如HTTP / 1.1 200 OK)
            // 和内容类型,以便客户端知道即将发生的事情,然后空一行:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

            // 显示HTML WEB网页
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // 使用CSS风格化表格
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}");
            client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }");
            client.println("th { padding: 12px; background-color: #0043af; color: white; }");
            client.println("tr { border: 1px solid #ddd; padding: 12px; }");
            client.println("tr:hover { background-color: #bcbcbc; }");
            client.println("td { border: none; padding: 12px; }");
            client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }");

            // Web 页面标题
            client.println("</style></head><body><h1>ESP8266 + BME280</h1>");
            client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>");
            client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
            client.println(bme.readTemperature());
            client.println(" *C</span></td></tr>");  
            client.println("<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">");
            client.println(1.8 * bme.readTemperature() + 32);
            client.println(" *F</span></td></tr>");       
            client.println("<tr><td>Pressure</td><td><span class=\"sensor\">");
            client.println(bme.readPressure() / 100.0F);
            client.println(" hPa</span></td></tr>");
            client.println("<tr><td>Approx. Altitude</td><td><span class=\"sensor\">");
            client.println(bme.readAltitude(SEALEVELPRESSURE_HPA));
            client.println(" m</span></td></tr>"); 
            client.println("<tr><td>Humidity</td><td><span class=\"sensor\">");
            client.println(bme.readHumidity());
            client.println(" %</span></td></tr>"); 
            client.println("</body></html>");

            // HTTP响应以另一个空行结尾
            client.println();
            // 跳出while循环
            break;
          } else { // 如果你有换行符,请清除currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // 如果除了回车符还有其他东西
          currentLine += c;      // 将其添加到currentLine的末尾
        }
      }
    }
    // 清除标题变量
    header = "";
    // 结束链接
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

运行效果

  • 查看ESP8266/32的IP
            上传代码后,打开串口监视器,在连接好WiFi后,将会输出所获取到的IP地址,如下图红框位置显示:

  • 打开网页
       从上图得知IP后,在手机或者电脑的浏览器内打开你的ESP8266/32的IP地址栏,如输入:192.168.10.163,打开后页面如下:


电脑(点击图片放大)

手机(点击图片放大)

    至此,项目就算完成了,这几天放假,我会尽量抽时间把剩余两篇文章写完,接下来再补充和说明一下本章内容.

补充与说明

  • 关于BEM280的I2C接口
    默认情况下,使用I2C通信协议需要创建一个名为bme的Adafruit_BME280对象:Adafruit_BME280 bme;.
    每个I2C设备都会有一个地址,代码里必须要地址正确才能够使用,例如下面代码中的0x76:

    status = bme.begin(0x76); 
    if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
    }

    此处建议先自己在arduino中提供的示例代码测试一下,因为市面上有些商家卖的传感器不一定跟我这里示例中的地址一致,所以事前需要把你的BME280地址扫描出来,以保证和代码中一直.这里提供一段关于扫描I2C设备的代码:

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}

void loop() {
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow error at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  }
  else {
    Serial.println("done\n");
  }
  delay(5000);          
}

扫描后,打开串口监视器即可查看被扫描到的设备地址,如果没有反应,那可能是你的传感器设备接错线了或有问题了,这个需要注意.

  • BME280的高度值
    #define SEALEVELPRESSURE_HPA (1013.25)
    这一行是定义一个变量,以保存海平面上的压力。为了更准确地估计海拔高度,使用前要先查询好你的所在位置的当前海平面压力,然后再替换该值,否则将显示不准确,此例中我并没有正确设置好,所以图中数值是错的。

  • 关于网站显示中文会乱码
    最快的方法是在HTTP头部加入<meta charset="utf-8"> 即:

    client.println("<head><meta charset=\"utf-8\" name=\"viewport\" content=\"width=device-width, initial-scale=1\">");

一沙一世界,一花一天堂。君掌盛无边,刹那成永恒。