Raspberry Pi:Примеры/Чип ESP8266, публикующий данные от DHT22 на Raspberry Pi

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Черновик


Чип ESP8266, публикующий данные от DHT22 на Raspberry Pi[1]

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

Для создания веб-сервера мы воспользуемся микрофреймворком Flask, использующим язык программирования Python. Ниже – схематичное изображение системы, которая должна получиться в итоге:

Вот так эта система выглядит в действии:

Тут видео

Настройка Raspberry Pi

О том, как начать работать с Raspberry Pi, читайте тут. О дополнительных настройках Raspberry Pi читайте тут.

Плата Raspberry Pi будет коммуницировать с ESP8266 при помощи протокола MQTT. Поэтому сначала устанавливаем брокер Mosquitto, а затем запускаем его в фоновом режиме:

pi@raspberry:~ $ mosquitto -d

Чтобы установить Flask, у вас должен быть установлен pip. Чтобы обновить Raspberry 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. Это значит, что вам не нужно будет обновлять веб-страницу, чтобы увидеть самые свежие данные, потому что они обновляются постоянно.

Чтобы установить пакет Flask SocketIO, впишите следующее:

pi@raspberrypi ~ $ sudo pip install flask-socketio

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

Этот скрипт – ключевая составляющая проекта. Он настраивает веб-сервер, а также при нажатии кyопок публикует 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 скрипт, показанный ниже:

#
# Создан Руи Сантосом (Rui Santos)
# Подробнее о проекте: http://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)

# Функция обратного вызова, которая будет вызвана, 
# когда клиент получит от сервера ответ CONNACK:
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

    # Если подписаться на on_connect(), то при потере соединения 
    # можно просто заново подключиться и восстановить подписки.
    client.subscribe("/esp8266/temperature")
    client.subscribe("/esp8266/humidity")

# Функция обратного вызова на тот случай, 
# если от ESP8266 придет сообщение PUBLISH:
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()

# Создаем словарь под названием «pins» для хранения данных о номерах, 
# названиях и состояниях контактов:
pins = {
   4 : {'name' : 'GPIO 4', 'board' : 'esp8266', 'topic' : 'esp8266/4', 'state' : 'False'},
   5 : {'name' : 'GPIO 5', 'board' : 'esp8266', 'topic' : 'esp8266/5', 'state' : 'False'}
   }

# Помещаем словарь «pins» в словарь «templateData»:
templateData = {
   'pins' : pins
   }

@app.route("/")
def main():
   # Передаем словарь «templateData» в HTML-шаблон,
   # а затем возвращаем его пользователю:
   return render_template('main.html', async_mode=socketio.async_mode, **templateData)

# Эта функция выполняется, когда кто-то запрашивает URL с информацией 
# о номере контакта и действии, которое нужно выполнить:
@app.route("/<board>/<changePin>/<action>")
def action(board, changePin, action):
   # Конвертируем контакт из URL в целое число:
   changePin = int(changePin)
   # Извлекаем данные о названии устройства:
   devicePin = pins[changePin]['name']
   # Если действие в URL – это «1», то выполняем этот код:
   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'
   # Помещаем в словарь «templateData» словарь «pins» и сообщение: 
   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 – это движок для HTML-шаблонов, который позволяет передавать динамические данные от скрипта 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

Копируем и вставляем в Raspberry Pi шаблон, показанный ниже:

<!DOCTYPE html>
<head>
   <title>RPi Web Server</title>
   <!-- Последний сжатый и скомпилированный CSS -->
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
   <!-- Опциональная тема -->
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
   <!-- Последний сжатый и скомпилированный 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».
  4. Переместите папку «pubsubclient» в папку «libraries» IDE Arduino

В комплекте с библиотекой идет несколько скетчей-примеров. Чтобы увидеть их, кликните в IDE Arduino на Файл > Примеры > PubSubClient (File > Examples > PubSubClient).

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

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

  1. Кликните здесь, чтобы скачать библиотеку. В результате на ваш компьютер, в папку «Загрузки», должен скачаться архив в формате ZIP.
  2. Распакуйте скачанный ZIP-архив. В результате у вас должна получиться папка под названием «DHT-sensor-library-master».
  3. Переименуйте ее на «DHT».
  4. Переместите папку «DHT» в папку «libraries» IDE Arduino.
  5. Снова откройте IDE Arduino.

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

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

/*****
 
 Все ресурсы для этого проекта здесь:
 https://randomnerdtutorials.com/
 
*****/

// загружаем библиотеки ESP8266WiFi, PubSubClient и DHT:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "DHT.h"

// убираем знаки комментария у одной из строчек ниже, 
// соответствующей типу датчика DHT:
//#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

// меняем эти константы, чтобы ESP8266 мог подключиться к роутеру:
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

// меняем переменную ниже на IP-адрес Raspberry Pi,
// чтобы она могла подключиться к брокеру MQTT:
const char* mqtt_server = "YOUR_RPi_IP_Address";

// инициализируем espClient:
WiFiClient espClient;
PubSubClient client(espClient);

// подключаем светодиоды к соответствующим GPIO-контактам ESP8266:
const int ledGPIO5 = 5;
const int ledGPIO4 = 4;

// датчик DHT:
const int DHTPin = 14;

// инициализируем датчик DHT:
DHT dht(DHTPin, DHTTYPE);

// вспомогательные переменные для таймера:
long now = millis();
long lastMeasure = 0;

// не меняйте функцию ниже; она подключает ESP8266 к роутеру:
void setup_wifi() {
  delay(10);
  // начинаем с подключения к WiFi-сети:
  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: ");
  //  "Подключение к WiFi выполнено – IP-адрес ESP8266: "
  Serial.println(WiFi.localIP());
}

// эта функция выполняется, когда какой-то девайс публикует сообщение
// в топик, на который подписан ESP8266; поменяйте функцию ниже 
// в соответствии с логикой вашей программы – чтобы, когда девайс 
// будет публиковать сообщение в топик, на который подписан ESP8266,
// ваша программа работала как нужно:
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();

  // если в вашем проекте не 2 светодиода, а больше, 
  // добавьте ниже больше кода:

  // если топик home/office/esp1/gpio2 получил сообщение, 
  // смотрим, что это за сообщение – «0» или «1»;
  // переключаем GPIO-контакт в соответствии с присланным значением:
  if(topic=="esp8266/4"){
      Serial.print("Changing GPIO 4 to ");
      // "Смена состояния GPIO-контакта 4 на "
      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 ");
      // "Смена состояния GPIO-контакта 5 на "
      if(messageTemp == "1"){
        digitalWrite(ledGPIO5, HIGH);
        Serial.print("On");
      }
      else if(messageTemp == "0"){
        digitalWrite(ledGPIO5, LOW);
        Serial.print("Off");
      }
  }
  Serial.println();
}

// эта функция переподключает ESP8266 к MQTT-брокеру; 
// измените эту функцию, если хотите, чтобы ESP8266 подписывался 
// на большее количество топиков:
void reconnect() {
  // заново запускаем цикл, пока не подключимся: 
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // "Попытка подключиться к MQTT-брокеру... "

    // Пытаемся подключиться:

          /*
     ЕСЛИ У ВАС ПРОБЛЕМЫ С ПОДКЛЮЧЕНИЕМ НЕСКОЛЬКИХ УСТРОЙСТВ
     К MQTT-БРОКЕРУ, ПОМЕНЯЙТЕ СТРОЧКУ НИЖЕ.

       *  Чтобы поменять ID чипа ESP8266, ему нужно дать 
          уникальное название. Так оно выглядит сейчас: 
       if (client.connect("ESP8266Client")) {

       *  Если вы хотите подключить к MQTT-брокеру 
          дополнительные устройства, его можно назвать так:
       if (client.connect("ESPOffice")) {

       *  Для других ESP:
       if (client.connect("ESPGarage")) {

       Это должно решить проблему с подключением 
       нескольких устройств к MQTT-брокеру:

       Также обратите внимание, что это название должно 
       соответствовать тому, что будет указано ниже, 
       в блоке loop().
          */

    if (client.connect("ESP8266Client")) {
      Serial.println("connected");  //  "подключен"
      // подписываемся или переподписываемся на топик;
      // можно подписаться не только на один, а на несколько топиков
      // (что касается конкретно этого примера, то это позволит
      // управлять большим количеством светодиодов):
      client.subscribe("esp8266/4");
      client.subscribe("esp8266/5");
    } else {
      Serial.print("failed, rc=");  //  "подключение не удалось"
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // "5 секунд до следующей попытки"
      // ждем 5 секунд перед тем, как попробовать снова: 
      delay(5000);
    }
  }
}

// эта функция настраивает GPIO-контакты ESP8266 
// на режим вывода данных, запускает последовательную коммуникацию
// на скорости 112500 бод, настраивает MQTT-брокер
// и задает функцию обратного вызова;
// функция обратного вызова служит для получения сообщений и,
// собственно, управления светодиодами:
void setup() {
  dht.begin();
  pinMode(ledGPIO4, OUTPUT);
  pinMode(ledGPIO5, OUTPUT);
  
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

// для этого проекта в функции loop() ничего менять не нужно;
// в сущности, эта функция устанавливает соединение 
// между ESP8266 и MQTT-брокером:
void loop() {
  if (!client.connected()) {
    reconnect();
  }
  if(!client.loop())

     /*
     ЕСЛИ У ВАС ПРОБЛЕМЫ С ПОДКЛЮЧЕНИЕМ НЕСКОЛЬКИХ УСТРОЙСТВ
     К MQTT-БРОКЕРУ, ПОМЕНЯЙТЕ СТРОЧКУ НИЖЕ.

       *  Чтобы поменять ID чипа ESP8266, ему нужно дать 
          уникальное название. Так оно выглядит сейчас: 
       if (client.connect("ESP8266Client")) {

       *  Если вы хотите подключить к MQTT-брокеру 
          несколько устройств, его можно назвать так:
       if (client.connect("ESPOffice")) {

       *  Для других ESP:
       if (client.connect("ESPGarage")) {

       Это должно решить проблему с подключением 
       нескольких устройств к MQTT-брокеру:

       Также обратите внимание, что это название должно 
       соответствовать тому, что будет указано ниже, 
       в блоке loop().
     */

    client.connect("ESP8266Client");
    
  now = millis();
  // публикуем новые данные о температуре и влажности 
  // каждые 10 секунд:
  if (now - lastMeasure > 10000) {
    lastMeasure = now;
    // данные от датчика могут отставать на 2 секунды
    // (это очень медленный датчик)
    float h = dht.readHumidity();
    // считываем температуру в Цельсиях (по умолчанию):
    float t = dht.readTemperature();
    // считываем температуру в Фаренгейтах (isFahrenheit = true):
    float f = dht.readTemperature(true);

    // проверяем, получилось ли прочитать данные с датчика,
    // и если нет, то завершаем операцию и пробуем снова:
    if (isnan(h) || isnan(t) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");
      //  "Не удалось прочесть данные с датчика!"
      return;
    }

    // рассчитываем температуру в Цельсиях:
    float hic = dht.computeHeatIndex(t, h, false);
    static char temperatureTemp[7];
    dtostrf(hic, 6, 2, temperatureTemp);
    
    // если нужно рассчитать температуру в Фаренгейтах,
    // убираем знаки комментирования у строчек ниже:
    // float hif = dht.computeHeatIndex(f, h);
    // static char temperatureTemp[7];
    // dtostrf(hic, 6, 2, temperatureTemp);
    
    static char humidityTemp[7];
    dtostrf(h, 6, 2, humidityTemp);

    // публикуем данные о температуре и влажности:
    client.publish("/esp8266/temperature", temperatureTemp);
    client.publish("/esp8266/humidity", humidityTemp);
    
    Serial.print("Humidity: ");  //  "Влажность: "
    Serial.print(h);
    Serial.print(" %\t Temperature: ");  //  " %\t Температура: "
    Serial.print(t);
    Serial.print(" *C ");
    Serial.print(f);
    Serial.print(" *F\t Heat index: ");  //  " *F\t Тепловой индекс: "
    Serial.print(hic);
    Serial.println(" *C ");
    // Serial.print(hif);
    // Serial.println(" *F");
  }
}

Схема

Чтобы завершить проект, вам понадобятся следующие компоненты:

  • Один модуль ESP8266 12Eна eBay
  • Один датчик DHT22на eBay
  • Один резистор на 4700 Ом
  • Два резистора на 470 Ом
  • Два светодиода

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

Вот схема проекта:

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

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

Чтобы запустить веб-сервер Raspberry Pi, переходим в папку, содержащую файл «app.py»:

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

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

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

Сразу после этого должен запуститься веб-сервер, и это должно произойти на порте «:8181».

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

Впишите в браузере IP-адрес Raspberry Pi. В моем случае это «http://192.168.1.98:8181».

Примечание: После IP-адреса обязательно должна быть приписка «:8181».

Вот видео того, как проект работает вживую:

Тут видео

См.также

Внешние ссылки