ESP8266:Примеры/Измерение внутренней и наружной температуры с помощью WeatherStation
Измерение внутренней и наружной температуры с помощью WeatherStation[1][2]
Предназначение программы, идущей вместе с WeatherStation – просто отображение информации. Это как бы исходная точка для новичков, которую можно дополнять и изменять под собственные нужды. К примеру, к программе можно пристроить специальный модуль, считывающий из интернета правильное время или информацию о текущей и будущей погоде. Но что если вам нужно измерить и показать информацию из разных мест дома или квартиры? Эта статья рассказывает, как считывать данные с датчика температуры/влажности DHT22 – и когда он подключен к WeatherStation напрямую, и когда он находится в другой комнате, а данные передает через WiFi.
Эта статья написана с прицелом на то, что вы уже работали с WorkingStation и датчиком DHT22.
Необходимые компоненты
- Одна погодная станция WeatherStation на базе ESP8266
- Один датчик температуры/влажности DHT22
- Одна адаптерная плата NodeMCU V1.0
Прямое подключение DHT22 к погодной станции WeatherStation
Если вы измеряете температуру и влажность в той же комнате, где находится WeatherStation, датчик DHT22 можно подключить следующим образом:
Теперь нужно импортировать библиотеку «DHT», и проще всего сделать это при помощи менеджера библиотек. Чтобы открыть его, кликните в IDE Arduino по Скетч > Подключить библиотеку > Управлять библиотеками... (Sketch > Include Library > Manage Libraries...).
Теперь давайте найдем библиотеку «DHT»:
Теперь, когда библиотека установлена в IDE Arduino, можно приступить к модификации скетча. Но перед тем, как продолжить, у вас должна быть полностью настроена WeatherStation. Отредактировав скетч, сохраните его под новым названием. Вот так скетч выглядит в отредактированном виде:
/** Лицензия MIT (MIT)
Копирайт (c) 2016, Дэниэл Эйкорн (Daniel Eichhorn)
Данная лицензия разрешает лицам, получившим копию данного программного
обеспечения и сопутствующей документации (в дальнейшем именуемыми
«Программное Обеспечение»), безвозмездно использовать Программное
Обеспечение без ограничений, включая неограниченное право на
использование, копирование, изменение, слияние, публикацию,
распространение, сублицензирование и/или продажу копий Программного
Обеспечения, а также лицам, которым предоставляется данное Программное
Обеспечение, при соблюдении следующих условий:
Указанное выше уведомление об авторском праве и данные условия должны
быть включены во все копии или значимые части данного Программного
Обеспечения.
ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ
КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ
ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ
НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ
СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО
КАКИМ-ЛИБО ИСКАМ, ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, В ТОМ ЧИСЛЕ,
ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ ИЗ-ЗА
ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОГРАММНЫМ
ОБЕСПЕЧЕНИЕМ.
Более подробно читайте на http://blog.squix.ch
*/
#include <ESP8266WiFi.h>
#include <Ticker.h>
#include <JsonListener.h>
#include "SSD1306Wire.h"
#include "OLEDDisplayUi.h"
#include "Wire.h"
#include "WundergroundClient.h"
#include "WeatherStationFonts.h"
#include "WeatherStationImages.h"
#include "TimeClient.h"
#include "ThingspeakClient.h"
#include "DHT.h"
/***************************
* Начальные настройки
**************************/
// о начальных настройках для WeatherStation читайте тут:
// http://blog.squix.org/weatherstation-getting-code-adapting-it
// константы для WiFi:
const char* WIFI_SSID = "X";
const char* WIFI_PWD = "X";
// интервал обновления – 10 минут (ограничение Wunderground):
const int UPDATE_INTERVAL_SECS = 10 * 60;
// константы для дисплея:
const int I2C_DISPLAY_ADDRESS = 0x3c;
const int SDA_PIN = D3;
const int SDC_PIN = D4;
// константа для библиотеки TimeClient:
const float UTC_OFFSET = 1;
// константы для библиотеки WundergroundClient:
const boolean IS_METRIC = true;
const String WUNDERGRROUND_API_KEY = "X";
const String WUNDERGRROUND_LANGUAGE = "DL";
const String WUNDERGROUND_COUNTRY = "CH";
const String WUNDERGROUND_CITY = "Zurich";
// константы для библиотеки ThingspeakClient:
const String THINGSPEAK_CHANNEL_ID = "X";
const String THINGSPEAK_API_READ_KEY = "X";
// инициализируем OLED-дисплей для адреса 0x3c
// (контакт SDA – это 14, а SDC – это 12):
SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
OLEDDisplayUi ui( &display );
// настройки для библиотеки «DHT»:
#define DHTPIN D6 // это цифровой контакт, к которому
// мы подключены; если используете не NodeMCU,
// а что-то другое, поменяйте D6
// на другой контакт
// уберите знаки комментария у типа датчика, который используете:
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
/***************************
* Финальные настройки
**************************/
TimeClient timeClient(UTC_OFFSET);
// если предпочитаете имперскую/дюймовую систему и Фаренгейты:
WundergroundClient wunderground(IS_METRIC);
ThingspeakClient thingspeak;
// инициализируем датчик температуры/влажности:
DHT dht(DHTPIN, DHTTYPE);
// флаг, который меняется в «маятниковой» функции каждые 10 минут:
bool readyForWeatherUpdate = false;
String lastUpdate = "--";
float humidity = 0.;
float temperature = 0.;
Ticker ticker;
// объявляем прототипы:
void drawProgress(OLEDDisplay *display, int percentage, String label);
void updateData(OLEDDisplay *display);
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawThingspeak(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
void setReadyForWeatherUpdate();
void drawIndoor(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
// добавляем фреймы;
// этот массив хранит указатели функции на все фреймы;
// фрейм – это блок данных, перемещающийся справа налево:
FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast, drawThingspeak, drawIndoor };
int numberOfFrames = 5;
OverlayCallback overlays[] = { drawHeaderOverlay };
int numberOfOverlays = 1;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
// инициализируем дисплей:
display.init();
display.clear();
display.display();
//display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.setContrast(255);
WiFi.begin(WIFI_SSID, WIFI_PWD);
int counter = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
display.clear();
display.drawString(64, 10, "Connecting to WiFi");
// "Подключение к WiFi"
display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
display.display();
counter++;
}
ui.setTargetFPS(30);
ui.setActiveSymbol(activeSymbole);
ui.setInactiveSymbol(inactiveSymbole);
// в этом аргументе можно выставить TOP, LEFT, BOTTOM и RIGHT:
ui.setIndicatorPosition(BOTTOM);
// здесь задаем, где будет находиться первый фрейм:
ui.setIndicatorDirection(LEFT_RIGHT);
// здесь задаем тип перехода (можно использовать варианты
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN):
ui.setFrameAnimation(SLIDE_LEFT);
ui.setFrames(frames, numberOfFrames);
ui.setOverlays(overlays, numberOfOverlays);
// функция ui.init() тоже принимает участие в инициализации дисплея:
ui.init();
Serial.println("");
updateData(&display);
ticker.attach(UPDATE_INTERVAL_SECS, setReadyForWeatherUpdate);
}
void loop() {
if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) {
updateData(&display);
}
int remainingTimeBudget = ui.update();
if (remainingTimeBudget > 0) {
// если у вас еще осталось время, здесь можно
// добавить еще кода для других задач:
delay(remainingTimeBudget);
}
}
void drawProgress(OLEDDisplay *display, int percentage, String label) {
display->clear();
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
display->drawString(64, 10, label);
display->drawProgressBar(2, 28, 124, 10, percentage);
display->display();
}
void updateData(OLEDDisplay *display) {
drawProgress(display, 10, "Updating time...");
// "Обновляем время..."
timeClient.updateTime();
drawProgress(display, 30, "Updating conditions...");
// "Обновляем условия..."
wunderground.updateConditions(WUNDERGRROUND_API_KEY, WUNDERGRROUND_LANGUAGE, WUNDERGROUND_COUNTRY, WUNDERGROUND_CITY);
drawProgress(display, 50, "Updating forecasts...");
// "Обновляем прогнозы..."
wunderground.updateForecast(WUNDERGRROUND_API_KEY, WUNDERGRROUND_LANGUAGE, WUNDERGROUND_COUNTRY, WUNDERGROUND_CITY);
drawProgress(display, 80, "Updating thingspeak...");
// "Обновляем ThingSpeak..."
thingspeak.getLastChannelItem(THINGSPEAK_CHANNEL_ID, THINGSPEAK_API_READ_KEY);
lastUpdate = timeClient.getFormattedTime();
drawProgress(display, 90, "Updating local temperature and humidity");
// "Обновляем местную температуру и влажность"
humidity = dht.readHumidity();
// считываем температуру в Фаренгейтах (isFahrenheit = true):
temperature = dht.readTemperature(!IS_METRIC);
readyForWeatherUpdate = false;
drawProgress(display, 100, "Done...");
// "Готово..."
delay(1000);
}
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
String date = wunderground.getDate();
int textWidth = display->getStringWidth(date);
display->drawString(64 + x, 5 + y, date);
display->setFont(ArialMT_Plain_24);
String time = timeClient.getFormattedTime();
textWidth = display->getStringWidth(time);
display->drawString(64 + x, 15 + y, time);
display->setTextAlignment(TEXT_ALIGN_LEFT);
}
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
display->setFont(ArialMT_Plain_10);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(60 + x, 5 + y, wunderground.getWeatherText());
display->setFont(ArialMT_Plain_24);
String temp = wunderground.getCurrentTemp() + "°C";
display->drawString(60 + x, 15 + y, temp);
int tempWidth = display->getStringWidth(temp);
display->setFont(Meteocons_Plain_42);
String weatherIcon = wunderground.getTodayIcon();
int weatherIconWidth = display->getStringWidth(weatherIcon);
display->drawString(32 + x - weatherIconWidth / 2, 05 + y, weatherIcon);
}
void drawIndoor(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
display->drawString(64 + x, 0 + y, "Indoor");
// "Внутри"
display->setFont(ArialMT_Plain_16);
display->drawString(64 + x, 10 + y, String(temperature, 1) + "°C");
display->drawString(64 + x, 30 + y, String(humidity, 1) + "%");
}
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
drawForecastDetails(display, x, y, 0);
drawForecastDetails(display, x + 44, y, 2);
drawForecastDetails(display, x + 88, y, 4);
}
void drawThingspeak(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
display->drawString(64 + x, 0 + y, "Outdoor");
// "Снаружи”
display->setFont(ArialMT_Plain_16);
display-> rawstring(64 + x, 10 + y, thingspeak.getFieldValue(0) + “°C”);
display-> rawstring(64 + x, 30 + y, thingspeak.getFieldValue(1) + “%”);
}
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
String day = wunderground.getForecastTitle(dayIndex).substring(0, 3);
day.toUpperCase();
display-> rawstring(x + 20, y, day);
display->setFont(Meteocons_Plain_21);
display-> rawstring(x + 20, y + 12, wunderground.getForecastIcon(dayIndex));
display->setFont(ArialMT_Plain_10);
display-> rawstring(x + 20, y + 34, wunderground.getForecastLowTemp(dayIndex) + “|” + wunderground.getForecastHighTemp(dayIndex));
display->setTextAlignment(TEXT_ALIGN_LEFT);
}
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
display->setColor(WHITE);
display->setFont(ArialMT_Plain_10);
String time = timeClient.getFormattedTime().substring(0, 5);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display-> rawstring(0, 54, time);
display->setTextAlignment(TEXT_ALIGN_RIGHT);
String temp = wunderground.getCurrentTemp() + “°C”;
display-> rawstring(128, 54, temp);
display->drawHorizontalLine(0, 52, 128);
}
void setReadyForWeatherUpdate() {
Serial.println(“Setting readyForUpdate to true”);
// “Установление readyForUpdate на «true»”
readyForWeatherUpdate = true;
}
Теперь подробнее об изменениях:
- Строчка 37: Я добавил библиотеку «DHT»
- Строчки 77-83: Настройки для библиотеки «DHT». Если используете DHT11 или DHT21, эти настройки можно поменять. Также можно поменять используемый контакт. Если не использовать контакт D6 на NodeMCU, при компиляции будет ошибка
- Строчки 104/105: Две новых переменных для влажности и температуры. Если используете новый модуль WeatherStation, эти переменные можно поместить в новые классы, а не в глобальное пространство...
- Строчки 110-119: Эти прототипы помогают компилятору/линкеру использовать функции, которые будут использоваться только потом
- Строчки 119/124: Я добавил новую функцию drawIndoor() и увеличил количество фреймов до 6. Благодаря этому фреймворк знает, какой метод вызвать, чтобы показать информацию о внутреннем помещении
- Строчки 231-233: В данном руководстве данные о температуре и влажности обновляются только один раз в 10 минут. Обновление данных обойдется нам лишь в 250 мс, но для цикла loop() это по-прежнему может быть слишком много. Но это можно делать и в while()
- Строчки 271-277: Этот фрагмент рисует фрейм с информацией о внутреннем помещении. Как видите, он очень похож на функцию drawThingspeak(). И это не случайно, т.к. они отображают похожие данные.
Вот и все! Теперь, когда код отредактирован, скомпилируйте его и загрузите на ESP8266.
Теперь давайте приступим ко второй части – созданию сенсорного «узла», который будет сначала постить данные в бесплатном облачном сервисе, а лишь затем выгружать их на WeatherStation, которая находится в другой комнате и будет показывать их на дисплее.
Необходимые компоненты
Для сенсорного «узла» вам потребуются:
- Одна адаптерная плата NodeMCU ESP8266 (или любой другой модуль с чипом ESP8266)
- Один датчик DHT22
- Три провода типа «мама-мама»
- Один источник питания с USB-разъемом
И я пишу это руководство с учетом того, что вы уже настроили рабочую WeatherStation.
Подключение компонентов
В подключении сенсорного «узла» нет ничего сложного. У платы датчика DHT есть только три контакта: GND («земля»), VCC (3,3-вольтовое напряжение) и DAT (это линия для передачи данных, подключаемая к контакту D6 на NodeMCU):
ThingSpeak
ThingSpeak – это бесплатный облачный сервис, позволяющий без особого труда постить данные, считанные с датчиков, а также визуализировать их и считывать при помощи простых HTTP-методов. Здесь стоит отметить, что для хранения погодных данных можно также использовать Wunderground, и способ этот, вероятно, будет даже проще, чем ThingSpeak. Но я все же считаю, что при использовании в образовательных целей ThingSpeak дает гораздо больше свободы. Ему можно отсылать данные, к примеру, от датчика движения и других подобных датчиков. Кроме того, в ThingSpeak встроена очень полезная функция, называемая «webhook» – она позволяет отправлять push-уведомления на смартфон, планшет и т.д.
Итак, вначале нужно зарегистрировать аккаунт в ThingSpeak (это бесплатно). Сделать это можно по этой ссылке. Зарегистрировавшись, залогиньтесь в новом аккаунте и перейдите к пункту «My Channels».
Затем кликните по кнопке «New Channel». Откроется новое меню. Заполните форму, которая находится в этом меню.
Поле «Name» (т.е. «название») поможет вам распознать свой канал среди множества других, которые вы, возможно, создали ранее. Другие важные элементы – названия в полях «Field1» и «Field2». Эти названия позднее будут отображены в графике, и с их помощью я сообщаю ThingSpeak, что значение, которое я отсылаю вместе с атрибутом «field1», должно быть показано как «Temperature» (т.е. температура).
Теперь перейдите к вкладке «API Keys» и запомните (а лучше – запишите) где-нибудь два ключа, сгенерированных в этом меню:
Первый ключ позволит вам записывать данные на этот ThingSpeak-канал, а второй – считывать данные с этого канала. Это секретные ключи, так что берегите их как зеницу ока. Зная эти ключи, злоумышленники могут заспамить ваш канал или даже «украсть» данные. К слову, сделав скриншот, я сгенерировал новые ключи :D
Итак, запишите ключи, скоро они нам понадобятся. Также запомните ID канала – он изображен в самом верху экрана.
Программирование сенсорного «узла»
Итак, теперь у нас есть все необходимые ингредиенты для постинга данных на ThingSpeak. Осталось лишь нужным образом запрограммировать ESP8266. Перейдите по этой ссылке и загрузите ZIP-файл с программой или воспользуйтесь командой git checkout (знающие поймут).
Теперь адаптируйте настройки под собственные нужды. Особенно это касается настроек WiFi и ключа «Write API», который мы сгенерировали и записали в разделе выше.
Интереса ради можно поиграться с интервалом обновления (в секундах). Имейте в виду, что минимальный интервал обновления в ThingSpeak составляет 15 секунд. Если задать интервал меньше, обновления будут просто игнорироваться. Теперь осталось записать программу на NodeMCU, и сенсорный «узел» должен начать работать. Чтобы посмотреть на результаты, можно вернуться к ThingSpeak и взглянуть на графики:
Показ данных на погодной станции
Теперь самое простое. Словно повар из кулинарной ТВ-передачи, я уже давно все приготовил за вас :) В библиотеке WeatherStation есть скетч WeatherStationDemo, который уже содержит все необходимое для отображения данных нашего сенсорного «узла».
Измените в нем строчки, в которые нужно вписать ключ «Read API» и ID канала, о которых шла речь в разделе «ThingSpeak» выше. Если вы не удалили фрагмент с сенсорным «узлом» из программы для WeatherStation, вам нужно просто загрузить на WeatherStation скетч с обновленным API-ключом и ID канала, и вуаля! Вы только что успешно отправили данные о температуре и влажности из соседней комнаты во внешний мир, а затем на маленький OLED-дисплей! Кстати, знаю пару людей (включая мою жену), которых этот трюк не впечатлит вовсе :D
Этот последний шаг может быть слегка непонятным, т.к. повар приготовил жаркое за несколько часов до начала ТВ-шоу, поэтому вот то же самое, но в замедленной съемке: библиотека WeatherStation теперь оснащена классом ThingSpeak, который берет на себя всю черновую работу. Вы «скармливаете» ему ID канала и API-ключ, а он загружает JSON-объект и выбирает только последний блок данных, т.к. именно он нам, в сущности, и нужен.
Чтобы было понятнее, можете взглянуть сюда. Возможно, вы даже захотите изменить/расширить этот код под собственные нужды.
Почему бы, к примеру, не замутить график, показывающий данные за последние 24 часа?
См.также
Внешние ссылки
ESP8266 AT-команды | |
---|---|
Список AT-команд | |
Базовые команды |
|
Команды для WiFi |
|
Команды для TCP/IP |
|