Въведение
В тази статия ще изградим компактна мини метеорологична станция с ESP8266-01, която измерва температура, относителна влажност, атмосферно налягане и надморска височина. За измерването се използват два популярни I2C сензора – AHT20 и BMP280.
Получените данни се публикуват чрез MQTT, което позволява лесното им използване в системи за домашна автоматизация, MQTT Panel, Node-RED, Home Assistant или собствена IoT система.
Проектът използва ESP8266-01, който е малък и евтин WiFi модул, но въпреки ограничените си размери може да изпълнява доста сложни задачи. В конкретния проект са реализирани:
- измерване на температура и влажност чрез AHT20;
- измерване на атмосферно налягане чрез BMP280;
- изчисляване на надморска височина;
- изчисляване на точка на оросяване;
- публикуване на данните чрез MQTT;
- автоматично конфигуриране на WiFi чрез WiFiManager;
- изтриване на WiFi настройките чрез хардуерен бутон;
- OTA обновяване на програмата;
- MQTT статус
online/offlineчрез Last Will and Testament (LWT); - възможност за използване на няколко станции чрез уникален
DEVICE_ID.
Това превръща ESP8266-01 в напълно функционална IoT метеорологична станция, която може да бъде поставена в различни помещения или на различни места и да изпраща данните към един общ MQTT брокер.
Схема на свързване на: ESP8266-01, AHT20, BMP280 и бутон за WiFi мениджър

Основен код на проекта: Мини метеорологична станция с ESP8266-01, AHT20, BMP280 и MQTT
Преди да продължим нпред трябва да инсталираме необходимите библиотеки за проекта. Може да ги инсталирате от “Manage Libraries…” от Arduino IDE или да ги свалите от тук, и да ги добавите ръчно. А за да разберете как да прибавите контролера към вашето Arduino IDE прочетете статията ни: Добавяне на ESP8266 в Arduino IDE: Стъпка по стъпка.
По-долу е основният код на проекта:
/*
* ==========================================================
* Метеорологична станция - ESP8266-01 + AHT20 + BMP280
* ==========================================================
*
* Функции:
* - Температура и влажност от AHT20 (адрес 0x38)
* - Налягане и надм. височина от BMP280 (адрес 0x77)
* - Точка на оросяване, изчислена от температура + влажност
* - Публикуване по MQTT на всеки 5 секунди
* - WiFiManager (captive portal) за конфигурация на WiFi
* - Хардуерен бутон на GPIO3 (RX), INPUT_PULLUP -> GND,
* задръжка 5 сек -> изтрива WiFi настройките и рестартира
* - OTA ъпдейт (ArduinoOTA)
* - Статус online/offline чрез LWT
*
* Нужни библиотеки (Library Manager):
* - WiFiManager by tzapu
* - PubSubClient by Nick O'Leary (knolleary)
* - Adafruit AHTX0
* - Adafruit BMP280 Library
* - Adafruit Unified Sensor
* - Adafruit BusIO
*
* Свързване (I2C, споделена шина за двата сензора):
* VCC -> 3.3V (модул с 3.3V логика!)
* GND -> GND
* SDA -> GPIO0
* SCL -> GPIO2
*
* Бутон за WiFi reset:
* GPIO3 (RX) -> бутон -> GND
* ==========================================================
*/
#include <ESP8266WiFi.h>
#include <WiFiManager.h> // tzapu/WiFiManager
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_AHTX0.h>
#include <Adafruit_BMP280.h>
// ---------------------------------------------------------
// ВАЖНО: смени номера на всяко устройство преди да го качиш!
// Устройство 1 -> "1", устройство 2 -> "2" и т.н.
// ---------------------------------------------------------
#define DEVICE_ID "1"
// ---------------------------------------------------------
// Настройки на MQTT сървъра
// ---------------------------------------------------------
const char* mqtt_server = "xxx.xxx.xxx.xxx";
const int mqtt_port = 1883;
const char* mqtt_user = "xxxxxxxxx";
const char* mqtt_pass = "xxxxxxxxx";
// ---------------------------------------------------------
// Пинова конфигурация
// ---------------------------------------------------------
#define I2C_SDA_PIN 0 // GPIO0
#define I2C_SCL_PIN 2 // GPIO2
#define WIFI_RESET_PIN 3 // GPIO3 (RX), INPUT_PULLUP, бутон към GND
#define RESET_HOLD_MS 5000 // 5 секунди задържане за reset
// ---------------------------------------------------------
// I2C адреси на сензорите (открити чрез I2C scanner)
// ---------------------------------------------------------
#define AHT20_ADDR 0x38
#define BMP280_ADDR 0x77
// ---------------------------------------------------------
// Интервал на публикуване - статичен, 5 секунди
// ---------------------------------------------------------
const unsigned long publishInterval = 5000;
unsigned long lastPublish = 0;
// ---------------------------------------------------------
// Глобални обекти
// ---------------------------------------------------------
WiFiClient espClient;
PubSubClient mqttClient(espClient);
Adafruit_AHTX0 aht;
Adafruit_BMP280 bmp;
bool ahtAvailable = false;
bool bmpAvailable = false;
// Топици - weatherStation/<DEVICE_ID>/...
String topicTemp;
String topicHum;
String topicPressure;
String topicAltitude;
String topicDewpoint;
String topicStatus;
// Стойност на морското ниво за изчисление на надм. височина (hPa)
#define SEALEVELPRESSURE_HPA (1013.25)
// ---------------------------------------------------------
// Изчисление на точка на оросяване (Magnus formula, апроксимация)
// ---------------------------------------------------------
float calculateDewPoint(float temperature, float humidity) {
float a = 17.27;
float b = 237.7;
float alpha = ((a * temperature) / (b + temperature)) + log(humidity / 100.0);
return (b * alpha) / (a - alpha);
}
// ---------------------------------------------------------
// WiFi reset бутон - неблокираща проверка в loop()
// ---------------------------------------------------------
void checkWifiResetButton() {
static unsigned long pressStart = 0;
static bool pressing = false;
bool pressed = (digitalRead(WIFI_RESET_PIN) == LOW);
if (pressed && !pressing) {
pressing = true;
pressStart = millis();
} else if (pressed && pressing) {
if (millis() - pressStart >= RESET_HOLD_MS) {
Serial.println(F("[WiFiReset] Бутонът е задържан 5 сек -> изтривам настройки и рестартирам..."));
WiFiManager wm;
wm.resetSettings();
delay(500);
ESP.restart();
}
} else if (!pressed && pressing) {
pressing = false; // пуснат преди 5 сек - нищо не се случва
}
}
// ---------------------------------------------------------
// Свързване към MQTT (с LWT за статус)
// ---------------------------------------------------------
void reconnectMqtt() {
static unsigned long lastAttempt = 0;
if (mqttClient.connected()) return;
if (millis() - lastAttempt < 5000) return; // 5 сек между опитите
lastAttempt = millis();
Serial.print(F("[MQTT] Свързване към брокера..."));
String clientId = "ESP8266_weatherStation_" + String(DEVICE_ID);
bool connected = mqttClient.connect(
clientId.c_str(),
mqtt_user, mqtt_pass,
topicStatus.c_str(), // will topic
1, // will QoS
true, // will retain
"offline" // will message
);
if (connected) {
Serial.println(F(" свързан!"));
mqttClient.publish(topicStatus.c_str(), "online", true);
} else {
Serial.printf(" неуспешно, rc=%d. Ще опитам пак...\n", mqttClient.state());
}
}
// ---------------------------------------------------------
// Публикуване на сензорните данни
// ---------------------------------------------------------
void publishSensorData() {
float temperature = NAN;
float humidity = NAN;
float pressure = NAN;
float altitude = NAN;
if (ahtAvailable) {
sensors_event_t humEvent, tempEvent;
aht.getEvent(&humEvent, &tempEvent);
temperature = tempEvent.temperature;
humidity = humEvent.relative_humidity;
} else {
Serial.println(F("[AHT20] Сензорът не е наличен, пропускам четенето."));
}
if (bmpAvailable) {
pressure = bmp.readPressure() / 100.0F; // Pa -> hPa
altitude = bmp.readAltitude(SEALEVELPRESSURE_HPA);
} else {
Serial.println(F("[BMP280] Сензорът не е наличен, пропускам четенето."));
}
char buf[16];
if (!isnan(temperature)) {
dtostrf(temperature, 4, 2, buf);
mqttClient.publish(topicTemp.c_str(), buf, true);
}
if (!isnan(humidity)) {
dtostrf(humidity, 4, 2, buf);
mqttClient.publish(topicHum.c_str(), buf, true);
}
if (!isnan(pressure)) {
dtostrf(pressure, 4, 2, buf);
mqttClient.publish(topicPressure.c_str(), buf, true);
}
if (!isnan(altitude)) {
dtostrf(altitude, 4, 2, buf);
mqttClient.publish(topicAltitude.c_str(), buf, true);
}
if (!isnan(temperature) && !isnan(humidity)) {
float dewpoint = calculateDewPoint(temperature, humidity);
dtostrf(dewpoint, 4, 2, buf);
mqttClient.publish(topicDewpoint.c_str(), buf, true);
}
Serial.printf("[Data] T=%.2fC H=%.2f%% P=%.2fhPa Alt=%.2fm\n",
temperature, humidity, pressure, altitude);
}
// ---------------------------------------------------------
// OTA настройка
// ---------------------------------------------------------
void setupOTA() {
ArduinoOTA.setHostname(("weatherStation-" + String(DEVICE_ID)).c_str());
// ArduinoOTA.setPassword("xxxxxxxxxxx"); // препоръчително - разкоментирай и сложи парола
ArduinoOTA.onStart([]() {
Serial.println(F("[OTA] Начало на ъпдейт..."));
});
ArduinoOTA.onEnd([]() {
Serial.println(F("\n[OTA] Край."));
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("[OTA] Прогрес: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("[OTA] Грешка [%u]\n", error);
});
ArduinoOTA.begin();
}
// ---------------------------------------------------------
// setup()
// ---------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(200);
Serial.println(F("\n\n=== Метеорологична станция ESP8266-01 (AHT20+BMP280) ==="));
// Бутон за WiFi reset - GPIO3 (RX)
pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
// Топици
String base = "weatherStation/" + String(DEVICE_ID) + "/";
topicTemp = base + "temp";
topicHum = base + "hum";
topicPressure = base + "pressure";
topicAltitude = base + "altitude";
topicDewpoint = base + "dewpoint";
topicStatus = base + "status";
Serial.println("[Info] Device ID: " + String(DEVICE_ID));
// I2C инициализация на кастомни пинове
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
// AHT20 (температура + влажност)
if (aht.begin(&Wire, 0, AHT20_ADDR)) {
ahtAvailable = true;
Serial.println(F("[AHT20] Сензорът е открит успешно."));
} else {
Serial.println(F("[AHT20] ГРЕШКА: сензорът не е открит! Провери свързването."));
}
// BMP280 (налягане + височина)
if (bmp.begin(BMP280_ADDR)) {
bmpAvailable = true;
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
Serial.println(F("[BMP280] Сензорът е открит успешно."));
} else {
Serial.println(F("[BMP280] ГРЕШКА: сензорът не е открит! Провери свързването."));
}
// WiFiManager - captive portal при първо пускане / липса на запазена мрежа
WiFiManager wm;
String apName = "WeatherStation-" + String(DEVICE_ID);
Serial.println(F("[WiFi] Стартирам WiFiManager..."));
if (!wm.autoConnect(apName.c_str())) {
Serial.println(F("[WiFi] Неуспешно свързване, рестартирам..."));
ESP.restart();
}
Serial.print(F("[WiFi] Свързан, IP: "));
Serial.println(WiFi.localIP());
// MQTT
mqttClient.setServer(mqtt_server, mqtt_port);
// OTA
setupOTA();
Serial.println(F("[Setup] Готово.\n"));
}
// ---------------------------------------------------------
// loop()
// ---------------------------------------------------------
void loop() {
ArduinoOTA.handle();
checkWifiResetButton();
if (!mqttClient.connected()) {
reconnectMqtt();
} else {
mqttClient.loop();
}
if (mqttClient.connected() && (millis() - lastPublish >= publishInterval)) {
lastPublish = millis();
publishSensorData();
}
}
Обяснение на кода
Използвани библиотеки
Проектът използва няколко библиотеки, всяка от които изпълнява конкретна функция:
#include <ESP8266WiFi.h>Това е основната WiFi библиотека за ESP8266.
#include <WiFiManager.h>WiFiManager позволява на устройството да се свърже с WiFi мрежа чрез captive portal. Това означава, че не е необходимо WiFi името и паролата да бъдат записани директно в кода.
#include <PubSubClient.h>Тази библиотека осигурява MQTT комуникацията между ESP8266-01 и MQTT брокера.
#include <ArduinoOTA.h>Позволява обновяване на firmware-а през WiFi, без да е необходимо ESP8266-01 да бъде свързан физически към USB-to-Serial адаптер.
#include <Wire.h>Използва се за I2C комуникацията с AHT20 и BMP280.
#include <Adafruit_AHTX0.h>
#include <Adafruit_BMP280.h>Това са библиотеките за двата сензора.
Уникален идентификатор на метеорологичната станция
#define DEVICE_ID "1"Това е една от важните настройки в проекта.
Ако използваме няколко метеорологични станции, всяка трябва да има различен идентификатор:
#define DEVICE_ID "1"#define DEVICE_ID "2"#define DEVICE_ID "3"Така всяко устройство ще използва собствен MQTT namespace:
weatherStation/1/
weatherStation/2/
weatherStation/3/Това позволява няколко ESP8266 модула да работят едновременно с един MQTT брокер, без да си пречат.
Свързване на AHT20 и BMP280
Двата сензора използват обща I2C шина.
В проекта са използвани:
#define I2C_SDA_PIN 0
#define I2C_SCL_PIN 2Следователно свързването е:
| ESP8266-01 | AHT20 | BMP280 |
|---|---|---|
| GPIO0 | SDA | SDA |
| GPIO2 | SCL | SCL |
| 3.3V | VCC | VCC |
| GND | GND | GND |
И двата сензора работят чрез I2C, което позволява да бъдат свързани паралелно към едни и същи SDA и SCL линии.
Разликата е, че използват различни I2C адреси:
#define AHT20_ADDR 0x38
#define BMP280_ADDR 0x77Това позволява на ESP8266 да комуникира с двата сензора по една и съща шина.
Измерване на температура и влажност с AHT20
AHT20 предоставя:
- температура;
- относителна влажност.
Четенето се извършва в:
aht.getEvent(
&humEvent,
&tempEvent
);
След това стойностите се записват:
temperature =
tempEvent.temperature;
humidity =
humEvent.relative_humidity;Температурата е в градуси Celsius, а влажността е в проценти.
Измерване на налягане и височина с BMP280
BMP280 измерва атмосферното налягане:
pressure =
bmp.readPressure() / 100.0F;Функцията readPressure() връща стойността в Pascal.
Тъй като стандартно атмосферното налягане се представя в hPa, стойността се разделя на 100:
1 hPa = 100 PaТака например:
101325 Pa = 1013.25 hPaИзчисляване на надморската височина
Височината се изчислява чрез:
altitude =
bmp.readAltitude(
SEALEVELPRESSURE_HPA
);В проекта е зададено:
#define SEALEVELPRESSURE_HPA 1013.25Това е стандартното атмосферно налягане на морското равнище.
Важно е да се знае, че изчислената надморска височина зависи от правилната референтна стойност на атмосферното налягане. За по-точни резултати стойността на морското ниво трябва да бъде коригирана според текущото локално налягане.
Изчисляване на точка на оросяване
Една от интересните функции на метеорологичната станция е изчисляването на точката на оросяване.
Това е температурата, при която въздухът достига 100% относителна влажност и започва кондензация на водната пара.
Изчислението се извършва чрез функцията:
float calculateDewPoint(
float temperature,
float humidity
)В нея е използвана апроксимация на формулата на Magnus.
mqttClient.publish(
topicDewpoint.c_str(),
buf,
true
);Точката на оросяване може да бъде полезна при:
- следене на риска от кондензация;
- контрол на отопление и вентилация;
- наблюдение на влажността в помещения;
- метеорологични приложения;
- автоматизация на оранжерии.
След изчисляването стойността се публикува чрез MQTT:
Работа с WiFiManager
Едно от големите предимства на проекта е, че WiFi настройките не са записани директно в програмата.
При първо стартиране:
WiFiManager wm;се създава временна WiFi мрежа:
WeatherStation-1Потребителят се свързва към тази мрежа и отваря captive portal, от който избира домашната WiFi мрежа и въвежда паролата.
След това WiFiManager запазва настройките във flash паметта на ESP8266.
При следващо стартиране устройството автоматично се свързва към запазената WiFi мрежа.
Хардуерен бутон за изтриване на WiFi настройките
В проекта е използван GPIO3, който е RX пинът на ESP8266-01:
#define WIFI_RESET_PIN 3Бутонът е свързан между: GPIO3 -> Бутон -> GND
Пинът е конфигуриран като:
pinMode(
WIFI_RESET_PIN,
INPUT_PULLUP
);Това означава, че в нормално състояние пинът е HIGH.
При натискане на бутона той става LOW.
За да не се изтрият настройките при случайно натискане, бутонът трябва да бъде задържан 5 секунди:
#define RESET_HOLD_MS 5000След изтичане на 5 секунди:
wm.resetSettings();изтрива запазените WiFi настройки.
След това:
ESP.restart();рестартира ESP8266.
При следващото стартиране устройството отново ще създаде WiFiManager captive portal.
MQTT връзка
MQTT връзката се управлява от функцията:
void reconnectMqtt()Преди свързване се проверява дали клиентът вече е свързан:
if (
mqttClient.connected()
)
return;Между отделните опити за свързване има интервал от 5 секунди:
if (
millis() -
lastAttempt < 5000
)
return;Това е важно, защото предотвратява постоянното блокиращо свързване към MQTT брокера.
MQTT Last Will and Testament
При свързването се задава MQTT статус:
topicStatus.c_str()Ако устройството загуби връзка неочаквано, брокерът автоматично публикува:
offlineПри успешно свързване ESP8266 публикува:
onlineТака други устройства или системи могат да разберат дали метеорологичната станция е активна.
Тази функция е особено полезна при:
- Home Assistant;
- Node-RED;
- MQTT Panel;
- собствени системи за мониторинг.
Публикуване на данните
Данните се публикуват на всеки 5 секунди:
const unsigned long publishInterval =
5000;В loop() се проверява:
if (
mqttClient.connected() &&
(
millis() -
lastPublish >=
publishInterval
)
)Това е неблокиращ подход, защото се използва millis() вместо delay().
Така ESP8266 може едновременно да:
- обслужва MQTT;
- обработва OTA заявки;
- проверява бутона;
- измерва сензорите;
- публикува данните.
MQTT топици и тяхното предназначение
Базовият MQTT топик за всяка метеорологична станция е:
weatherStation/<DEVICE_ID>/Например, ако DEVICE_ID е 1, всички топици ще започват с:
weatherStation/1/MQTT топици
| MQTT топик | Описание |
|---|---|
weatherStation/1/temp | Публикува измерената температура от сензора AHT20 в градуси Целзий (°C). |
weatherStation/1/hum | Публикува относителната влажност на въздуха, измерена от AHT20, в проценти (%). |
weatherStation/1/pressure | Публикува атмосферното налягане, измерено от BMP280, в хектопаскали (hPa). |
weatherStation/1/altitude | Публикува изчислената надморска височина в метри (m), базирана на измереното атмосферно налягане. |
weatherStation/1/dewpoint | Публикува изчислената точка на оросяване, получена на базата на температурата и влажността. |
weatherStation/1/status | Показва текущото състояние на устройството. Стойността е online при успешно свързване към MQTT брокера и offline, ако връзката бъде прекъсната неочаквано (LWT). |

Пример за публикувани стойности
| MQTT топик | Примерна стойност |
|---|---|
weatherStation/1/temp | 24.56 |
weatherStation/1/hum | 48.32 |
weatherStation/1/pressure | 1012.45 |
weatherStation/1/altitude | 125.34 |
weatherStation/1/dewpoint | 13.20 |
weatherStation/1/status | online / offline |
Благодарение на използването на DEVICE_ID, към един и същ MQTT брокер могат да бъдат свързани множество метеорологични станции, като всяка публикува данните си в собствен набор от топици, например:
weatherStation/1/temp
weatherStation/2/temp
weatherStation/3/tempТова позволява лесно разграничаване на данните от различните устройства и тяхната последваща обработка в Home Assistant, Node-RED, MQTT Explorer, MQTT Panel или други IoT платформи.
OTA обновяване на ESP8266-01
Проектът включва и OTA обновяване:
ArduinoOTA.begin();След като ESP8266-01 се свърже към WiFi, програмата може да бъде обновявана без физическо свързване чрез USB-to-Serial адаптер.
Името на устройството се създава динамично:
weatherStation-1Ако имаме второ устройство:
weatherStation-2Това улеснява разпознаването на различните устройства в Arduino IDE.
При необходимост е препоръчително да се активира OTA парола:
ArduinoOTA.setPassword(
"YOUR_OTA_PASSWORD"
);Това предотвратява неоторизирано обновяване на firmware-а в локалната мрежа.
Обобщение
В този проект създадохме компактна IoT метеорологична станция с ESP8266-01, която използва два I2C сензора:
- AHT20 за температура и влажност;
- BMP280 за атмосферно налягане и изчисляване на надморска височина.
Станцията изпраща данните към MQTT брокер на всеки 5 секунди.
Чрез уникалния DEVICE_ID могат да бъдат изградени няколко станции, например:
weatherStation/1/
weatherStation/2/
weatherStation/3/Допълнителните функции правят проекта много по-практичен:
- WiFiManager премахва необходимостта от записване на WiFi парола в кода;
- хардуерният бутон позволява лесно изтриване на WiFi настройките;
- OTA позволява безжично обновяване;
- MQTT LWT показва дали устройството е онлайн;
- точката на оросяване предоставя допълнителна метеорологична информация.
Подробен пример за публикуване на данни в MQTT брокер е наличен в това ръководство за ESP8266 и MQTT от Random Nerd Tutorials.
Заключение
ESP8266-01 е малък WiFi модул, но с помощта на AHT20, BMP280 и MQTT може да се превърне в напълно функционална мини метеорологична станция.
Използването на MQTT прави проекта много гъвкав. Данните могат да бъдат визуализирани в MQTT Panel, обработвани чрез Node-RED или интегрирани в по-голяма система за домашна автоматизация.
Комбинацията от ESP8266-01 + AHT20 + BMP280 е особено подходяща за проекти, при които са важни малките размери, ниската цена и възможността за безжично предаване на данните.
Добавянето на WiFiManager, OTA обновяване и MQTT статус превръща проекта от обикновен сензорен модул в завършена IoT метеорологична станция, която може да работи автономно и да бъде разширявана с допълнителни сензори и функции.
