Этот проект сделан на основе погодной станции [https://blog.squix.org/2015/05/esp8266-projects-internet-connected.html ] В этой версии пришлось внести небольшие изменения в код, чтобы информация, выводимая на экран, была ориентирована в обратную сторону, из-за другого расположения OLED-дисплея на домике. Посмотреть как работает погодная станция можно по ссылке [VIDEO
Соберите следующую схему
Этот проект потребует большого количества дополнительных библиотек и дополнительных файлов, в которых описаны специальные символы, для вывода информации о погоде. Информацию о прогнозе погоды в этом скетче получают с сервера openweathermap. Рекомендуется зарегистрироваться на нём, чтобы по ключу получать информацию о погоде. Код основного скетча – следующий:
#include <Arduino.h>
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <coredecls.h>
#else
#include <WiFi.h>
#endif
#include <ESPHTTPClient.h>
#include <JsonListener.h>
// time
#include <time.h> // time() ctime()
#include <sys/time.h> // struct timeval
#include <SSD1306.h>
#include "OLEDDisplayUi.h"
#include <Wire.h>
#include "OpenWeatherMapCurrent.h"
#include "OpenWeatherMapForecast.h"
#include "WeatherStationFonts.h"
#include "WeatherStationImages.h"
/***************************
* Начальные установки
**************************/
// WIFI
const char* WIFI_SSID = "***********";
const char* WIFI_PWD = "***********";
#define TZ 2 // (utc+) Сдвиг по времени для Московской зоны
#define DST_MN 60 // сдвиг на 60 минут для некоторых стран
// Setup
const int UPDATE_INTERVAL_SECS = 20 * 60; // Обновление данных каждые 20 мин
// Установки дисплея
const int I2C_DISPLAY_ADDRESS = 0x3c;
#if defined(ESP8266)
const int SDA_PIN = D2;
const int SDC_PIN = D1;
#else
const int SDA_PIN = 5; //D3;
const int SDC_PIN = 4; //D4;
#endif
// OpenWeatherMap установки
// API ключ находится по адресу:
// https://docs.thingpulse.com/how-tos/openweathermap-key/
String OPEN_WEATHER_MAP_APP_ID = "**********************************";
/*
Локация вашего места может быть найдена по адресу
https://openweathermap.org/find?q=
В этом коде использована локация 559853
https://openweathermap.org/city/559853.
*/
String OPEN_WEATHER_MAP_LOCATION_ID = "559853";
// Выберите язык:
// Arabic - ar, Bulgarian - bg, Catalan - ca, Czech - cz, German - de, Greek - el,
// English - en, Persian (Farsi) - fa, Finnish - fi, French - fr, Galician - gl,
// Croatian - hr, Hungarian - hu, Italian - it, Japanese - ja, Korean - kr,
// Latvian - la, Lithuanian - lt, Macedonian - mk, Dutch - nl, Polish - pl,
// Portuguese - pt, Romanian - ro, Russian - ru, Swedish - se, Slovak - sk,
// Slovenian - sl, Spanish - es, Turkish - tr, Ukrainian - ua, Vietnamese - vi,
// Chinese Simplified - zh_cn, Chinese Traditional - zh_tw.
String OPEN_WEATHER_MAP_LANGUAGE = "ru";
const uint8_t MAX_FORECASTS = 4;
const boolean IS_METRIC = true;
// const String WDAY_NAMES[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
// Используем вместо русских букв, английские и небольшие хитрости вместо букв П и Ч
const String WDAY_NAMES[] = {"BC", "!]H", "BT", "CP", "4T", "!]T", "C5"};
const String MONTH_NAMES[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
/***************************
* Установки окончены
**************************/
// Инициализация дисплея по адресу 0x3c
SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
OLEDDisplayUi ui( &display );
OpenWeatherMapCurrentData currentWeather;
OpenWeatherMapCurrent currentWeatherClient;
OpenWeatherMapForecastData forecasts[MAX_FORECASTS];
OpenWeatherMapForecast forecastClient;
#define TZ_MN ((TZ)*60)
#define TZ_SEC ((TZ)*3600)
#define DST_SEC ((DST_MN)*60)
time_t now;
// Этот флаг меняется каждые 10 минут
bool readyForWeatherUpdate = false;
String lastUpdate = "--";
long timeSinceLastWUpdate = 0;
//функции, описанные далее в скетче
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 drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
void setReadyForWeatherUpdate();
// В станции используется 3 фрейма
// Вывод времени, вывод текущей погоды, вывод прогнноза на 3 дня
FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast };
int numberOfFrames = 3;
OverlayCallback overlays[] = { drawHeaderOverlay };
int numberOfOverlays = 1;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
// Инициализация дисплея
display.init();
display.flipScreenVertically();
display.clear();
display.display();
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, 3, "Connecting to 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++;
}
// Считываем время с NTP-сервера
configTime(TZ_SEC, DST_SEC, "pool.ntp.org");
ui.setTargetFPS(30);
ui.setActiveSymbol(activeSymbole);
ui.setInactiveSymbol(inactiveSymbole);
// Допустимые значения
// TOP, LEFT, BOTTOM, RIGHT
ui.setIndicatorPosition(TOP);
ui.setIndicatorDirection(LEFT_RIGHT);
ui.setFrameAnimation(SLIDE_LEFT);
ui.setFrames(frames, numberOfFrames);
ui.setOverlays(overlays, numberOfOverlays);
ui.init();
Serial.println("");
updateData(&display);
}
void loop() {
if (millis() - timeSinceLastWUpdate > (1000L*UPDATE_INTERVAL_SECS)) {
setReadyForWeatherUpdate();
timeSinceLastWUpdate = millis();
}
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, 25, label);
display->drawProgressBar(2, 43, 124, 10, percentage);
display->flipScreenVertically();
display->display();
}
void updateData(OLEDDisplay *display) {
drawProgress(display, 10, "Updating time...");
drawProgress(display, 30, "Updating weather...");
currentWeatherClient.setMetric(IS_METRIC);
currentWeatherClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
currentWeatherClient.updateCurrentById(¤tWeather, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID);
drawProgress(display, 50, "Updating forecasts...");
forecastClient.setMetric(IS_METRIC);
forecastClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
uint8_t allowedHours[] = {12};
forecastClient.setAllowedHours(allowedHours, sizeof(allowedHours));
forecastClient.updateForecastsById(forecasts, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID, MAX_FORECASTS);
readyForWeatherUpdate = false;
drawProgress(display, 100, "Done...");
delay(1000);
}
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { // Рисуем дату и время
now = time(nullptr);
struct tm* timeInfo;
timeInfo = localtime(&now);
char buff[16];
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
String date = WDAY_NAMES[timeInfo->tm_wday];
sprintf_P(buff, PSTR("%s, %02d/%02d/%04d"), WDAY_NAMES[timeInfo->tm_wday].c_str(), timeInfo->tm_mday, timeInfo->tm_mon+1, timeInfo->tm_year + 1900);
display->drawString(64 + x, 15 + y, String(buff));
display->setFont(ArialMT_Plain_24);
sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
display->drawString(64 + x, 25 + y, String(buff));
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_CENTER);
display->drawString(47 + x, 53 + y, currentWeather.description);
display->setFont(ArialMT_Plain_24);
display->setTextAlignment(TEXT_ALIGN_LEFT);
String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
display->drawString(43 + x, 20 + y, temp);
display->setFont(Meteocons_Plain_36);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(15 + x, 15 + y, currentWeather.iconMeteoCon);
}
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
drawForecastDetails(display, x, y, 0);
drawForecastDetails(display, x + 44, y, 1);
drawForecastDetails(display, x + 88, y, 2);
}
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) { // Рисуем прогноз погоды на 3 дня
time_t observationTimestamp = forecasts[dayIndex].observationTime;
struct tm* timeInfo;
timeInfo = localtime(&observationTimestamp);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
display->drawString(x + 20, y+15, WDAY_NAMES[timeInfo->tm_wday]);
display->setFont(Meteocons_Plain_21);
display->drawString(x + 20, y + 27, forecasts[dayIndex].iconMeteoCon);
String temp = String(forecasts[dayIndex].temp, 0) + (IS_METRIC ? "°C" : "°F");
display->setFont(ArialMT_Plain_10);
display->drawString(x + 20, y + 49, temp);
display->setTextAlignment(TEXT_ALIGN_LEFT);
}
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) { // Рисуем строку статуса
now = time(nullptr);
struct tm* timeInfo;
timeInfo = localtime(&now);
char buff[14];
sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min); // Время в левой части статусной строки
display->setColor(WHITE);
display->setFont(ArialMT_Plain_10);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(0, 1, String(buff));
display->setTextAlignment(TEXT_ALIGN_RIGHT);
String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F"); // Текущая температура в правой части
display->drawString(128, 1, temp);
display->drawHorizontalLine(0, 14, 128); // Чертим разделительную линию
}
void setReadyForWeatherUpdate() {
Serial.println("Setting readyForUpdate to true");
readyForWeatherUpdate = true;
}
Дополнительные файлы проекта необходимо поместить в ту же папку, где находится основной скетч. Это файл со специальными символами, которые используются в скетче – сохранить в файле с названием WeatherStationImpages.h
#define WiFi_Logo_width 60
#define WiFi_Logo_height 36
const uint8_t WiFi_Logo_bits[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0x03, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0x07, 0xC0, 0x83, 0x01, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00,
0xC0, 0xFF, 0xFF, 0x7C, 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x31, 0x46, 0x7C,
0xFC, 0x77, 0x08, 0x00, 0xE0, 0x23, 0xC6, 0x3C, 0xFC, 0x67, 0x18, 0x00,
0xE0, 0x23, 0xE4, 0x3F, 0x1C, 0x00, 0x18, 0x00, 0xE0, 0x23, 0x60, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xE0, 0x03, 0x60, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x07, 0x60, 0x3C, 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C,
0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x8F, 0x71, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xC0, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x08, 0x00,
0xC0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0x1F,
0x00, 0x00, 0x06, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x07, 0x00,
0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF8, 0xFF, 0xFF,
0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00,
0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
const uint8_t activeSymbole[] PROGMEM = {
B00000000,
B00000000,
B00011000,
B00100100,
B01000010,
B01000010,
B00100100,
B00011000
};
const uint8_t inactiveSymbole[] PROGMEM = {
B00000000,
B00000000,
B00000000,
B00000000,
B00011000,
B00011000,
B00000000,
B00000000
};
Архив, с описанием доступных локаций, для которых можно по коду узнать погоду для вашей местности можно скачать отсюда: [http://bulk.openweathermap.org/sample/ ]
И самый большой файл с описанием иконок, которые используются на OpenWeatherMap, файл с названием WeatherStationFonts.h скачать можно отсюда: [](https://)