ESP8266 публикация показаний DHT22 через MQTT на Raspberry Pi

В этом проекте вы создадите автономный веб-сервер на Raspberry Pi, который отображает показания температуры и влажности с датчика DHT22. Вы также сможете управлять двумя выходами ESP8266 с помощью протокола MQTT.

Для создания веб-сервера вы будете использовать Python-микрофреймворк Flask. Вот общая схема системы:

Обзорная схема проекта DHT22 Raspberry Pi ESP8266

Сначала посмотрите видеодемонстрацию

Рекомендуемые ресурсы:

Если вам нравится домашняя автоматизация и вы хотите построить полноценную систему домашней автоматизации, рекомендую скачать мой курс по домашней автоматизации.

Базовая настройка Raspberry Pi

Прежде чем продолжить чтение этого проекта, убедитесь, что на вашем Raspberry Pi установлена операционная система Raspbian.

Вы можете прочитать моё Руководство по началу работы с Raspberry Pi, чтобы установить Raspbian и выполнить базовую настройку.

Запуск и установка брокера Mosquitto

Raspberry Pi будет взаимодействовать с ESP8266 по протоколу MQTT. Имея установленным брокером Mosquitto, вам нужно запустить брокер Mosquitto в фоновом режиме:

pi@raspberry:~ $ mosquitto -d

Установка Flask

Мы будем использовать Python-микрофреймворк Flask для превращения Raspberry Pi в веб-сервер.

Для установки Flask вам потребуется установленный pip. Выполните следующие команды для обновления Pi и установки pip:

pi@raspberrypi ~ $ sudo apt-get update
pi@raspberrypi ~ $ sudo apt-get upgrade
pi@raspberrypi ~ $ sudo apt-get install python-pip python-flask git-core

Затем используйте pip для установки Flask и Paho MQTT:

pi@raspberrypi ~ $ sudo pip install flask
pi@raspberrypi ~ $ sudo pip install paho-mqtt

Установка SocketIO

В этом проекте используется SocketIO, который позволяет создать веб-страницу Python Flask, которая может асинхронно обновляться вашим приложением Python Flask. Это означает, что вам не нужно обновлять веб-страницу для просмотра последних показаний — они обновляются мгновенно. Вы будете устанавливать Python-пакет Flask SocketIO.

pi@raspberrypi ~ $ sudo pip install flask-socketio

Создание Python-скрипта

Это основной скрипт нашего приложения. Он настраивает веб-сервер, и при нажатии кнопок публикует MQTT-сообщение на ESP8266. Он также подписан на MQTT-топики температуры и влажности для получения показаний.

Для поддержания порядка начните с создания новой папки:

pi@raspberrypi ~ $ mkdir web-server
pi@raspberrypi ~ $ cd web-server
pi@raspberrypi:~/web-server $

Создайте новый файл с именем app.py.

pi@raspberrypi:~/web-server $ nano app.py

Скопируйте и вставьте следующий скрипт на ваш Raspberry Pi:

#
# Created by Rui Santos
# Complete project details: https://randomnerdtutorials.com
#

import paho.mqtt.client as mqtt
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

    # Subscribing in on_connect() means that if we lose the connection and
    # reconnect then subscriptions will be renewed.
    client.subscribe("/esp8266/temperature")
    client.subscribe("/esp8266/humidity")

# The callback for when a PUBLISH message is received from the ESP8266.
def on_message(client, userdata, message):
    #socketio.emit('my variable')
    print("Received message '" + str(message.payload) + "' on topic '"
        + message.topic + "' with QoS " + str(message.qos))
    if message.topic == "/esp8266/temperature":
        print("temperature update")
        socketio.emit('dht_temperature', {'data': message.payload})
    if message.topic == "/esp8266/humidity":
        print("humidity update")
        socketio.emit('dht_humidity', {'data': message.payload})

mqttc=mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_message = on_message
mqttc.connect("localhost",1883,60)
mqttc.loop_start()

# Create a dictionary called pins to store the pin number, name, and pin state:
pins = {
   4 : {'name' : 'GPIO 4', 'board' : 'esp8266', 'topic' : 'esp8266/4', 'state' : 'False'},
   5 : {'name' : 'GPIO 5', 'board' : 'esp8266', 'topic' : 'esp8266/5', 'state' : 'False'}
   }

# Put the pin dictionary into the template data dictionary:
templateData = {
   'pins' : pins
   }

@app.route("/")
def main():
   # Pass the template data into the template main.html and return it to the user
   return render_template('main.html', async_mode=socketio.async_mode, **templateData)

# The function below is executed when someone requests a URL with the pin number and action in it:
@app.route("/<board>/<changePin>/<action>")
def action(board, changePin, action):
   # Convert the pin from the URL into an integer:
   changePin = int(changePin)
   # Get the device name for the pin being changed:
   devicePin = pins[changePin]['name']
   # If the action part of the URL is "1" execute the code indented below:
   if action == "1" and board == 'esp8266':
      mqttc.publish(pins[changePin]['topic'],"1")
      pins[changePin]['state'] = 'True'
   if action == "0" and board == 'esp8266':
      mqttc.publish(pins[changePin]['topic'],"0")
      pins[changePin]['state'] = 'False'
   # Along with the pin dictionary, put the message into the template data dictionary:
   templateData = {
      'pins' : pins
   }
   return render_template('main.html', **templateData)

@socketio.on('my event')
def handle_my_custom_event(json):
    print('received json data here: ' + str(json))

if __name__ == "__main__":
   socketio.run(app, host='0.0.0.0', port=8181, debug=True)

Посмотреть исходный код

Создание HTML-файла

Хранение HTML-тегов отдельно от Python-скрипта — это способ поддерживать организованность проекта. Flask использует шаблонизатор Jinja2, который можно использовать для отправки динамических данных из Python-скрипта в HTML-файл.

Создайте новую папку с именем templates:

pi@raspberrypi:~/web-server $ mkdir templates
pi@raspberrypi:~/web-server $ cd templates
pi@raspberrypi:~/web-server/templates $

Создайте новый файл с именем main.html.

pi@raspberrypi:~/web-server/templates $ nano main.html

Скопируйте и вставьте следующий шаблон на ваш Pi:

<!DOCTYPE html>
<head>
   <title>RPi Web Server</title>
   <!-- Latest compiled and minified CSS -->
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
   <!-- Optional theme -->
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
   <!-- Latest compiled and minified JavaScript -->
   <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
   <script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
   <script type="text/javascript" charset="utf-8">
      $(document).ready(function() {
         var socket = io.connect('http://' + document.domain + ':' + location.port);
         socket.on('connect', function() {
          socket.emit('my event', {data: 'I\'m connected!'});
         });
         socket.on('dht_temperature', function(msg) {
      var nDate = new Date();
          $('#readingsUpdated').text(nDate.getHours() + 'h:' + nDate.getMinutes() +
             'm:' + nDate.getSeconds() + 's').html();
          $('#temperature').text(msg.data).html();
         });
         socket.on('dht_humidity', function(msg) {
          $('#humidity').text(msg.data).html();
         });
      });
   </script>
</head>

<body>
   <h1>RPi Web Server - ESP8266 MQTT</h1>
   {% for pin in pins %}
   <h2>{{ pins[pin].name }}
   {% if pins[pin].state == 'True' %}
      is currently <strong>on</strong></h2><div class="row"><div class="col-md-2">
      <a href="/esp8266/{{pin}}/0" class="btn btn-block btn-lg btn-default" role="button">Turn off</a></div></div>
   {% else %}
      is currently <strong>off</strong></h2><div class="row"><div class="col-md-2">
      <a href="/esp8266/{{pin}}/1" class="btn btn-block btn-lg btn-primary" role="button">Turn on</a></div></div>
   {% endif %}
   {% endfor %}
   <h3>DHT Readings (updated <span id="readingsUpdated"></span>)</h3>
   <h3>Temperature: <span id="temperature"></span>ºC</h3>
   <h3>Humidity: <span id="humidity"></span>%</h3>
</body>
</html>

Посмотреть исходный код

Программирование ESP8266

Для взаимодействия ESP8266 с веб-сервером Raspberry Pi вам нужно установить библиотеку PubSubClient. Эта библиотека предоставляет клиент для простого обмена сообщениями по принципу публикация/подписка с сервером, поддерживающим MQTT (по сути, позволяет вашему ESP8266 общаться с Python веб-сервером).

Установка библиотеки PubSubClient

1) Нажмите здесь, чтобы скачать библиотеку PubSubClient. У вас должна появиться папка .zip в папке «Загрузки».

2) Распакуйте папку .zip, и вы получите папку pubsubclient-master.

3) Переименуйте папку из pubsubclient-master в pubsubclient.

4) Переместите папку pubsubclient в папку libraries вашей установки Arduino IDE.

Библиотека поставляется с рядом примеров скетчей. Смотрите File > Examples > PubSubClient в программе Arduino IDE.

Установка библиотеки датчика DHT

Библиотека DHT sensor предоставляет простой способ использования любого датчика DHT для чтения температуры и влажности с платами ESP8266 или Arduino.

1) Нажмите здесь, чтобы скачать библиотеку DHT sensor. У вас должна появиться папка .zip в папке «Загрузки».

2) Распакуйте папку .zip, и вы получите папку DHT-sensor-library-master.

3) Переименуйте папку из DHT-sensor-library-master в DHT.

4) Переместите папку DHT в папку libraries вашей установки Arduino IDE.

5) Затем перезапустите Arduino IDE.

Загрузка скетча

Наконец, вы можете загрузить полный скетч на ваш ESP8266 (замените на ваш SSID, пароль и IP-адрес RPi):

/*****

 All the resources for this project:
 https://randomnerdtutorials.com/

*****/

// Loading the ESP8266WiFi library and the PubSubClient library
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "DHT.h"

// Uncomment one of the lines bellow for whatever DHT sensor type you're using!
//#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

// Change the credentials below, so your ESP8266 connects to your router
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

// Change the variable to your Raspberry Pi IP address, so it connects to your MQTT broker
const char* mqtt_server = "YOUR_RPi_IP_Address";

// Initializes the espClient
WiFiClient espClient;
PubSubClient client(espClient);

// Connect an LED to each GPIO of your ESP8266
const int ledGPIO5 = 5;
const int ledGPIO4 = 4;

// DHT Sensor
const int DHTPin = 14;

// Initialize DHT sensor.
DHT dht(DHTPin, DHTTYPE);

// Timers auxiliar variables
long now = millis();
long lastMeasure = 0;

// Don't change the function below. This functions connects your ESP8266 to your router
void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("WiFi connected - ESP IP address: ");
  Serial.println(WiFi.localIP());
}

// This functions is executed when some device publishes a message to a topic that your ESP8266 is subscribed to
// Change the function below to add logic to your program, so when a device publishes a message to a topic that
// your ESP8266 is subscribed you can actually do something
void callback(String topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;

  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }
  Serial.println();

  // Feel free to add more if statements to control more GPIOs with MQTT

  // If a message is received on the topic home/office/esp1/gpio2, you check if the message is either 1 or 0. Turns the ESP GPIO according to the message
  if(topic=="esp8266/4"){
      Serial.print("Changing GPIO 4 to ");
      if(messageTemp == "1"){
        digitalWrite(ledGPIO4, HIGH);
        Serial.print("On");
      }
      else if(messageTemp == "0"){
        digitalWrite(ledGPIO4, LOW);
        Serial.print("Off");
      }
  }
  if(topic=="esp8266/5"){
      Serial.print("Changing GPIO 5 to ");
      if(messageTemp == "1"){
        digitalWrite(ledGPIO5, HIGH);
        Serial.print("On");
      }
      else if(messageTemp == "0"){
        digitalWrite(ledGPIO5, LOW);
        Serial.print("Off");
      }
  }
  Serial.println();
}

// This functions reconnects your ESP8266 to your MQTT broker
// Change the function below if you want to subscribe to more topics with your ESP8266
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
     /*
     YOU  NEED TO CHANGE THIS NEXT LINE, IF YOU'RE HAVING PROBLEMS WITH MQTT MULTIPLE CONNECTIONS
     To change the ESP device ID, you will have to give a unique name to the ESP8266.
     Here's how it looks like now:
       if (client.connect("ESP8266Client")) {
     If you want more devices connected to the MQTT broker, you can do it like this:
       if (client.connect("ESPOffice")) {
     Then, for the other ESP:
       if (client.connect("ESPGarage")) {
      That should solve your MQTT multiple connections problem

     THE SECTION IN loop() function should match your device name
    */
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Subscribe or resubscribe to a topic
      // You can subscribe to more topics (to control more LEDs in this example)
      client.subscribe("esp8266/4");
      client.subscribe("esp8266/5");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

// The setup function sets your ESP GPIOs to Outputs, starts the serial communication at a baud rate of 115200
// Sets your mqtt broker and sets the callback function
// The callback function is what receives messages and actually controls the LEDs
void setup() {
  dht.begin();
  pinMode(ledGPIO4, OUTPUT);
  pinMode(ledGPIO5, OUTPUT);

  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

// For this project, you don't need to change anything in the loop function.
// Basically it ensures that you ESP is connected to your broker
void loop() {
  if (!client.connected()) {
    reconnect();
  }
  if(!client.loop())
     /*
     YOU  NEED TO CHANGE THIS NEXT LINE, IF YOU'RE HAVING PROBLEMS WITH MQTT MULTIPLE CONNECTIONS
     To change the ESP device ID, you will have to give a unique name to the ESP8266.
     Here's how it looks like now:
       client.connect("ESP8266Client");
     If you want more devices connected to the MQTT broker, you can do it like this:
       client.connect("ESPOffice");
     Then, for the other ESP:
       client.connect("ESPGarage");
      That should solve your MQTT multiple connections problem

     THE SECTION IN recionnect() function should match your device name
    */
    client.connect("ESP8266Client");

  now = millis();
  // Publishes new temperature and humidity every 10 seconds
  if (now - lastMeasure > 10000) {
    lastMeasure = now;
    // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
    float h = dht.readHumidity();
    // Read temperature as Celsius (the default)
    float t = dht.readTemperature();
    // Read temperature as Fahrenheit (isFahrenheit = true)
    float f = dht.readTemperature(true);

    // Check if any reads failed and exit early (to try again).
    if (isnan(h) || isnan(t) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");
      return;
    }

    // Computes temperature values in Celsius
    float hic = dht.computeHeatIndex(t, h, false);
    static char temperatureTemp[7];
    dtostrf(hic, 6, 2, temperatureTemp);

    // Uncomment to compute temperature values in Fahrenheit
    // float hif = dht.computeHeatIndex(f, h);
    // static char temperatureTemp[7];
    // dtostrf(hic, 6, 2, temperatureTemp);

    static char humidityTemp[7];
    dtostrf(h, 6, 2, humidityTemp);

    // Publishes Temperature and Humidity values
    client.publish("/esp8266/temperature", temperatureTemp);
    client.publish("/esp8266/humidity", humidityTemp);

    Serial.print("Humidity: ");
    Serial.print(h);
    Serial.print(" %\t Temperature: ");
    Serial.print(t);
    Serial.print(" *C ");
    Serial.print(f);
    Serial.print(" *F\t Heat index: ");
    Serial.print(hic);
    Serial.println(" *C ");
    // Serial.print(hif);
    // Serial.println(" *F");
  }
}

Посмотреть исходный код

Схема подключения

Для выполнения этого проекта вам понадобятся следующие компоненты:

Вы можете использовать приведённые выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все детали для ваших проектов по лучшей цене!

Примечание: другие типы датчиков DHT также будут работать с небольшим изменением в коде.

Вот схема подключения:

Схема подключения ESP8266 с датчиком температуры и влажности DHT22

Важно: для правильной работы датчик DHT требует 5 В, поэтому убедитесь, что вы используете вывод Vin вашего ESP8266, который выдаёт 5 В.

Запуск веб-сервера

Для запуска веб-сервера Raspberry Pi перейдите в папку, содержащую файл app.py:

pi@raspberrypi:~/web-server/templates $ cd ..

Затем выполните следующую команду:

pi@raspberrypi:~/web-server $ sudo python app.py

Ваш веб-сервер должен запуститься немедленно на порту :8181!

Демонстрация

Откройте адрес вашего Raspberry Pi в браузере, введя его IP-адрес, в моём случае: http://192.168.1.98:8181

Примечание: вы должны ввести ваш IP-адрес, за которым следует :8181

Веб-сервер Raspberry Pi с ESP8266 MQTT — показания температуры и влажности

Вот видеодемонстрация веб-сервера в действии:

Заключение

В следующей статье мы будем публиковать показания датчиков с ESP8266 на Python веб-сервер, но эти показания будут храниться в базе данных SQLite.

Если вам нравится домашняя автоматизация, узнайте больше о Node-RED, Raspberry Pi, ESP8266 и Arduino с нашим курсом: курс по домашней автоматизации.

У вас есть вопросы? Оставьте комментарий ниже!