Raspberry Pi:Примеры/Чип ESP8266, публикующий данные от DHT22 на Raspberry Pi
Содержание | Введение | Продукты | Операционная система | Настройка | Основы Linux | Аппаратные средства | Неисправности | Типовые проблемы | Часто возникающие вопросы | Библиотеки | Примеры |
Черновик |
Чип 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
- Чтобы скачать библиотеку PubSubClient, кликните здесь. В результате на ваш компьютер, в папку «Загрузки», должен скачаться архив в формате ZIP.
- Распакуйте скачанный ZIP-архив. В результате у вас должна получиться папка под названием «pubsubclient-master».
- Переименуйте эту папку на «pubsubclient».
- Переместите папку «pubsubclient» в папку «libraries» IDE Arduino
В комплекте с библиотекой идет несколько скетчей-примеров. Чтобы увидеть их, кликните в IDE Arduino на Файл > Примеры > PubSubClient (File > Examples > PubSubClient).
Установка библиотеки для датчика DHT
Эта библиотека упрощает использование любого датчика DHT для считывания данных о температуре и влажности при помощи ESP8266 или платы Arduino.
- Кликните здесь, чтобы скачать библиотеку. В результате на ваш компьютер, в папку «Загрузки», должен скачаться архив в формате ZIP.
- Распакуйте скачанный ZIP-архив. В результате у вас должна получиться папка под названием «DHT-sensor-library-master».
- Переименуйте ее на «DHT».
- Переместите папку «DHT» в папку «libraries» IDE Arduino.
- Снова откройте 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».
Вот видео того, как проект работает вживую: