MQTT ile kendi hava istasyonunuzu kurduğunuzda, herhangi bir dashboard'un, ev otomasyon sisteminin veya sulama kontrolcüsünün abone olabileceği yerel rüzgar, yağmur, sıcaklık, nem ve basınç ölçümleri elde edersiniz. Bu eğitimde yaygın hava sensörlerini bir ESP32'ye bağlayacak, dakikada bir JSON mesajını bir broker'a yayınlayacak ve ölçümleri kaydedip grafik çizmek için bir Python aboneliği kullanacaksınız. Her şey önce ücretsiz public broker üzerinde test ediliyor, ardından özel kimlik bilgilerine taşınıyor.
MQTT Hava İstasyonu Düğümü İçin Parçalar ve Kablolama
Tasarım, kolay bulunan ve iyi belgelenmiş parçalar kullanır. Rüzgar ve yağmur sensörleri klasik "weather meter" kiti tarzındadır: her dönüşte veya her devrilmede kısa süreliğine kapanan reed anahtarlar kullanan bir kepçeli anemometre ve devrilen kovalı bir yağmur ölçer.
| Parça | Amaç | ESP32 bağlantısı |
|---|---|---|
| ESP32 DevKit kartı | Wi-Fi ve MQTT | USB veya 5 V besleme |
| BME280 modülü (I2C) | Sıcaklık, nem, basınç | SDA GPIO 21, SCL GPIO 22, 3.3 V, GND |
| Kepçeli anemometre (reed anahtar) | Rüzgar hızı | Bir kablo GPIO 27'ye, diğeri GND'ye |
| Devrilen kovalı yağmur ölçer | Yağış | Bir kablo GPIO 26'ya, diğeri GND'ye |
| Radyasyon kalkanı | BME280'i doğrudan güneşten korur | Mekanik |
| Hava koşullarına dayanıklı kutu | ESP32'yi korur | Mekanik |
Reed anahtarların yalnızca bir pull-up direncine ihtiyacı vardır; sketch ESP32'nin dahili pull-up'larını etkinleştirir, böylece anahtar kapandığında pin LOW'a çekilir. Bu kitlerde çoğunlukla bir rüzgar gülü de bulunur; bir ADC piniyle okunan bir direnç merdiveni kullanır ve temel kurulum çalıştıktan sonra eklenebilir.
İki kalibrasyon sabiti sensörün datasheet'inden gelir. Burada kullanılan yaygın kitte saniyede bir anemometre kapanması 2,4 km/s'ye, bir kova devrilmesi ise 0,2794 mm yağışa karşılık gelir. Donanımınız farklıysa sketch'in başındaki iki sabiti değiştirin.
Topic ve Payload Tasarımı
Kompakt bir JSON payload'a sahip tek bir telemetri topic'i ve MQTT Last Will ile yönetilen retained bir durum topic'i kullanın:
connectmq/demo/<random-id>/weather/station1/telemetry (JSON every 60 s)
connectmq/demo/<random-id>/weather/station1/status (online/offline, retained)
{
"temp_c": 14.8,
"hum_pct": 71.2,
"press_hpa": 1012.6,
"wind_kmh": 9.6,
"gust_kmh": 16.8,
"rain_mm": 0.28,
"rain_today_mm": 3.07,
"rssi": -61,
"uptime_s": 86412
}
Her darbeyi değil dakikalık ortalamaları göndermek trafiği çok küçük tutar ve public broker'ın saniyede 10 mesaj ve mesaj başına 64 KB limitlerinin rahatça altında kalır. Bir hamle (gust) değeri (dakika içindeki en yüksek 3 saniyelik ortalama), ortalama almanın aksi halde gizleyeceği bilgiyi korur. Ücretsiz public MQTT broker rehberi, 8 topic seviyesi ve istemci başına 10 abonelik gibi diğer limitleri listeler; bu tasarım bu limitlerin de rahatça içinde kalır.
Telemetri mesajı neden retained yapılmıyor? Yapabilirsiniz ve bir hava istasyonu için çoğu zaman faydalıdır: iki yayın arasında açılan bir dashboard boş bir widget yerine son ölçümü hemen gösterir. Bunun bedeli, saatler önce çökmüş bir istasyonun hâlâ son değerlerini raporluyormuş gibi görünmesidir; Last Will ile ayrı bir retained status topic'inin önemli olmasının nedeni tam olarak budur. İkisini birlikte gösterin ve durum offline olduğunda ölçümü soluk gösterin.
ESP32 Sketch'i
Arduino Library Manager'dan şu kütüphaneleri kurun: Nick O'Leary'nin PubSubClient kütüphanesi, Adafruit BME280 Library ve Adafruit Unified Sensor. Wi-Fi bilgilerini ve topic önekindeki rastgele kimliği değiştirin.
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/k3x9q2/weather/station1";
const int PIN_WIND = 27;
const int PIN_RAIN = 26;
const float KMH_PER_HZ = 2.4; // anemometer: 1 closure/s = 2.4 km/h
const float MM_PER_TIP = 0.2794; // rain gauge: 1 tip = 0.2794 mm
WiFiClient net;
PubSubClient mqtt(net);
Adafruit_BME280 bme;
volatile uint32_t windCount = 0, rainCount = 0;
volatile uint32_t lastWindUs = 0, lastRainMs = 0;
void IRAM_ATTR onWind() {
uint32_t now = micros();
if (now - lastWindUs > 5000) { windCount++; lastWindUs = now; } // 5 ms debounce
}
void IRAM_ATTR onRain() {
uint32_t now = millis();
if (now - lastRainMs > 200) { rainCount++; lastRainMs = now; } // 200 ms debounce
}
char clientId[32];
char statusTopic[96];
float rainTodayMm = 0;
float maxGust = 0;
uint32_t lastGustCount = 0, lastGustMs = 0, lastPublishMs = 0, windAtPublish = 0;
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(clientId, nullptr, nullptr, statusTopic, 1, true, "offline")) {
mqtt.publish(statusTopic, "online", true);
} else {
Serial.printf("MQTT failed, state=%d\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_WIND, INPUT_PULLUP);
pinMode(PIN_RAIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_WIND), onWind, FALLING);
attachInterrupt(digitalPinToInterrupt(PIN_RAIN), onRain, FALLING);
if (!bme.begin(0x76) && !bme.begin(0x77)) {
Serial.println("BME280 not found, check wiring");
}
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
snprintf(clientId, sizeof(clientId), "wx-%llx", ESP.getEfuseMac());
snprintf(statusTopic, sizeof(statusTopic), "%s/status", PREFIX);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
connectMqtt();
lastPublishMs = lastGustMs = millis();
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
uint32_t now = millis();
// Gust: highest 3-second average within the publish window
if (now - lastGustMs >= 3000) {
noInterrupts(); uint32_t c = windCount; interrupts();
float gust = (c - lastGustCount) / ((now - lastGustMs) / 1000.0) * KMH_PER_HZ;
if (gust > maxGust) maxGust = gust;
lastGustCount = c; lastGustMs = now;
}
if (now - lastPublishMs >= 60000) {
noInterrupts();
uint32_t w = windCount; uint32_t r = rainCount; rainCount = 0;
interrupts();
float seconds = (now - lastPublishMs) / 1000.0;
float windKmh = (w - windAtPublish) / seconds * KMH_PER_HZ;
float rainMm = r * MM_PER_TIP;
rainTodayMm += rainMm; // reset at midnight from your logger or with NTP
char payload[256];
snprintf(payload, sizeof(payload),
"{\"temp_c\":%.1f,\"hum_pct\":%.1f,\"press_hpa\":%.1f,"
"\"wind_kmh\":%.1f,\"gust_kmh\":%.1f,\"rain_mm\":%.2f,"
"\"rain_today_mm\":%.2f,\"rssi\":%d,\"uptime_s\":%lu}",
bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0,
windKmh, maxGust, rainMm, rainTodayMm, WiFi.RSSI(), now / 1000);
char topic[96];
snprintf(topic, sizeof(topic), "%s/telemetry", PREFIX);
mqtt.publish(topic, payload);
Serial.println(payload);
windAtPublish = w;
maxGust = 0;
lastPublishMs = now;
}
}
Seri monitörü 115200 baud'da açın. Anemometreyi elle çevirin ve yağmur kovasını birkaç kez devirin; sonraki dakikanın mesajında sıfırdan farklı değerler görünmelidir. Başka bir terminalde broker'ın verileri aldığını doğrulayın:
mosquitto_sub -h broker.connectmq.com -p 1883 -i wx-viewer-5521 -t "connectmq/demo/k3x9q2/weather/#" -v
Kart hiç bağlanmıyorsa ESP32 MQTT eğitimi Wi-Fi ve broker sorunlarının giderilmesini adım adım anlatır.
Ölçümleri Kaydetme ve Grafiğe Dökme
Broker canlı mesajları iletir; sizin için geçmiş verileri saklamaz. Kısa bir Python aboneliği her ölçümü bir CSV dosyasına ekleyebilir ve bir grafik oluşturabilir. Bağımlılıkları kurun:
pip install "paho-mqtt>=2.0" matplotlib
import csv
import json
import os
import sys
from datetime import datetime, timezone
import paho.mqtt.client as mqtt
TOPIC = "connectmq/demo/k3x9q2/weather/station1/telemetry"
CSV_FILE = "weather.csv"
FIELDS = ["time", "temp_c", "hum_pct", "press_hpa", "wind_kmh", "gust_kmh", "rain_mm"]
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code)
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
data = json.loads(msg.payload)
row = {"time": datetime.now(timezone.utc).isoformat(timespec="seconds")}
row.update({k: data.get(k) for k in FIELDS[1:]})
new_file = not os.path.exists(CSV_FILE)
with open(CSV_FILE, "a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS)
if new_file:
writer.writeheader()
writer.writerow(row)
print(row)
def plot():
import matplotlib.pyplot as plt
times, temp, wind, gust, rain = [], [], [], [], []
with open(CSV_FILE) as f:
for r in csv.DictReader(f):
times.append(datetime.fromisoformat(r["time"]))
temp.append(float(r["temp_c"]))
wind.append(float(r["wind_kmh"]))
gust.append(float(r["gust_kmh"]))
rain.append(float(r["rain_mm"]))
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex=True, figsize=(10, 8))
ax1.plot(times, temp); ax1.set_ylabel("Temp (C)")
ax2.plot(times, wind, label="wind"); ax2.plot(times, gust, label="gust")
ax2.set_ylabel("km/h"); ax2.legend()
ax3.bar(times, rain, width=0.0007); ax3.set_ylabel("Rain (mm/min)")
fig.tight_layout()
fig.savefig("weather.png", dpi=120)
print("Saved weather.png")
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "plot":
plot()
else:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="wx-logger-8812")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_forever()
Veri toplamak için python weather_logger.py, weather.png dosyasını üretmek için python weather_logger.py plot komutunu çalıştırın. Kalıcı bir kurulum için birçok kişi CSV yerine Telegraf ile beslenen InfluxDB'yi veya bir Node-RED dashboard'unu tercih eder; her ikisi de broker'a sıradan MQTT istemcileri olarak bağlanır.
Doğruluk Kontrol Listesi
Bir hava istasyonu ancak konumlandırması kadar iyidir. Rakamlara güvenmeden önce bu listeyi gözden geçirin:
- Sıcaklık sensörünü koruyun. Doğrudan güneş altındaki veya kapalı bir kutunun içindeki BME280 birkaç derece yüksek ölçer. Havalandırmalı bir radyasyon kalkanı kullanın.
- Anemometreyi yükseğe ve açık bir yere monte edin. Çatılar ve çitler türbülans oluşturur; yakındaki engellerin üzerinde kalan bir direk çok daha temsili rüzgar verisi sağlar.
- Yağmur ölçeri terazide tutun ve yağmuru engelleyen veya damlatan duvarlardan ve ağaçlardan uzak yerleştirin.
- ESP32'yi BME280'den uzak tutun. Aynı kutuyu paylaşırlarsa kartın kendi ısısı ölçümleri bozar.
- Yazılımda debounce uygulayın ve kabloları kontrol edin. Uzun ve ekransız kablolar, hayalet yağmur devrilmeleri gibi görünen gürültü toplar.
- Günlük yağış toplamını tutarlı biçimde sıfırlayın; ya logger'da yerel gece yarısında ya da ESP32'de NTP saatiyle.
MQTT Hava İstasyonu Kurulumunuzu Genişletme
Temel istasyon kararlı hale geldikten sonra birkaç ekleme onu belirgin şekilde daha kullanışlı yapar.
Rüzgar yönü. Çoğu kitteki rüzgar gülü döndükçe dahili dirençler arasında geçiş yapar. Onu sabit bir dirençle (kit datasheet'i bir değer önerir) gerilim bölücü olarak GPIO 34 gibi bir ADC1 pinine bağlayın, analogRead() ile okuyun ve her gerilim aralığını 16 pusula yönünden birine eşleyin. Yalnızca ADC1 pinlerini kullanın, çünkü ESP32'de Wi-Fi etkinken ADC2 kullanılamaz. Yönü derece olarak yayınlayın; grafikler için "NNE"den daha kullanışlıdır.
Güneş paneli ve batarya ile besleme. Küçük bir panel, bir lityum hücre ve bir şarj kontrolcüsü istasyonu şebekeden bağımsız çalıştırabilir; ancak Wi-Fi'si sürekli açık bir ESP32 kışın küçük bir panel için çok fazla güç çeker. Yayınlar arasında deep sleep moduna geçin ve anemometre darbelerini ULP yardımcı işlemcisiyle veya ayrı bir sayaç çipiyle sayın ya da daha seyrek bir yayın aralığını kabul edin. Sorunları önceden görebilmek için batarya gerilimini JSON'a ekleyin.
Günlük özetler. Logger'ın gece yarısından hemen sonra ayrı bir topic'e retained bir günlük özet (minimum ve maksimum sıcaklık, toplam yağış, en yüksek hamle) yayınlamasını sağlayın. Ev otomasyon araçları böylece tüm geçmişi okumadan dünkü yağışa bakarak bir sulama döngüsünü atlayabilir.
Türetilmiş değerler. Çiy noktası ve "hissedilen" sıcaklık, logger'da veya Node-RED'de sıcaklık ve nemden kolayca hesaplanabilir. Bunları ESP32 üzerinde değil sonraki aşamada hesaplayın; böylece formülü yeniden flash'lamadan değiştirebilirsiniz.
İstasyonu Test Broker'ından Çıkarma
Public broker kurulumu doğrulamak için idealdir, ancak üzerindeki her şey herkes tarafından okunabilir ve tek ayrımınız topic önekidir. Kalıcı bir istasyon için, özellikle de bir otomatik sulama sistemini besleyen bir istasyon için, TLS ve kimlik bilgileri olan özel bir broker kullanın. ConnectMQ gibi bir barındırılan MQTT broker bunu evde sunucu çalıştırmadan sağlar. İstemciyi WiFiClientSecure, 8883 portu ve portaldan alınan kullanıcı adı, parola ve client ID ile değiştirin:
#include <WiFiClientSecure.h>
WiFiClientSecure net; // replace WiFiClient
// in setup(): load the ISRG Root X1 certificate for Let's Encrypt
// net.setCACert(ISRG_ROOT_X1_PEM);
mqtt.setServer("broker.connectmq.com", 8883);
// connect with: mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", statusTopic, 1, true, "offline");
ConnectMQ portalındaki canlı topic gezgini her mesajı geldiği anda gösterir ve eşik tabanlı uyarı kuralları, ek kod yazmadan sizi don veya kuvvetli rüzgar konusunda uyarabilir. Tarıma yönelik daha fazla fikir akıllı tarım IoT makalesinde ve daha kapsamlı IoT otomasyon rehberinde yer alıyor.
Hava Verilerinizi ConnectMQ'da Yayınlayın
ConnectMQ; TLS, tarayıcı dashboard'ları için WebSocket erişimi ve hızlı kontroller için tarayıcı içi istemci sunan yönetilen bir MQTT broker'dır. app.connectmq.com adresinden kredi kartı gerekmeden ücretsiz bir Serverless broker oluşturun ve istasyonunuzu dakikalar içinde public test alanından özel kimlik bilgilerine taşıyın.
Sık Sorulan Sorular
Bir DIY hava istasyonu MQTT üzerinden ne sıklıkla yayın yapmalı?
Ortalama değerler için dakikada bir iyi bir varsayılandır. Ortalamanın yanında maksimum 3 saniyelik rüzgar hızını da gönderirseniz trafik çok küçük kalır ve hamleler yine de yakalanır.
Sıcaklık ölçümlerim neden çok yüksek?
Neredeyse her zaman güneş ısınması veya ESP32'nin kendi ısısı yüzünden. BME280'i mikrodenetleyiciden ve voltaj regülatöründen uzakta, havalandırmalı bir radyasyon kalkanının içine taşıyın.
BME280 yerine BME680 veya SHT31 kullanabilir miyim?
Evet. Kütüphaneyi ve okuma fonksiyonlarını değiştirin; JSON alan adlarını koruduğunuz sürece MQTT tarafı aynı kalır.
Broker hava durumu geçmişimi saklar mı?
Hayır. MQTT broker'lar canlı mesajları ve son retained değeri iletir. Geçmişi yukarıdaki Python logger gibi bir abonelikle, bir zaman serisi veritabanıyla birlikte Telegraf ile veya Node-RED ile saklayın.



