Uygulamalı Eğitimler

Ethernet Shield (W5100/W5500) ve PubSubClient ile Arduino MQTT

W5100/W5500 Ethernet Shield'lı Arduino Uno veya Mega'yı PubSubClient ile MQTT broker'a bağlayın: bağlantı, DHCP, yeniden bağlanma, RAM sınırları ve test.

Güncellendi · 8 dk okuma

Ethernet Shield (W5100/W5500) ve PubSubClient ile Arduino MQTT

Ethernet Shield takılı bir Arduino Uno veya Mega, bir sensörü ya da röleyi MQTT'ye bağlamanın hâlâ en güvenilir yollarından biridir: kopan Wi-Fi yok, radyo paraziti yok; bir pano içinde ya da bodrumda sorunsuz çalışan kablolu bir bağlantı var. Standart yığın, WIZnet çipi için Ethernet kütüphanesi ile MQTT için PubSubClient'tır.

Bu eğitim bir W5100 veya W5500 shield'ı broker'a bağlıyor, bir sensör değeri yayınlıyor ve komut kabul ediyor; çekilen kablolara ve broker yeniden başlatmalarına dayanan bir yeniden bağlanma döngüsüyle. Ardından her şeyi tarayıcıdan uçtan uca doğruluyorsunuz.

Donanım ve kütüphaneler

ParçaNotlar
Arduino Uno R3 veya Mega 2560Uno: 2 KB RAM, Mega: 8 KB RAM
Ethernet Shield W5100 veya Ethernet Shield 2 (W5500)SPI ile konuşur, chip select 10 numaralı pinde
DHCP'li bir router'a ağ kablosuYa da LAN'ınızda statik bir IP

Library Manager'dan iki kütüphane kurun: Ethernet (Arduino'nun; W5100, W5200 ve W5500'ü destekler) ve PubSubClient (Nick O'Leary'nin). Çoğu shield'da chip select'i 4 numaralı pin olan bir microSD yuvası da bulunur; kart kullanmıyorsanız SPI hattını bozmaması için pin 4'ü HIGH yapın.

Sketch'in tamamı

ethernet_mqtt.ino
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>

// Ağınızdaki her kart için benzersiz bir MAC kullanın
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x01 };

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "arduino-eth-0001";
const char* MQTT_USER = "username";
const char* MQTT_PASS = "password";

const char* T_STATUS = "testmqtt/arduino-eth/0001/status";
const char* T_TEMP   = "testmqtt/arduino-eth/0001/temp";
const char* T_CMD    = "testmqtt/arduino-eth/0001/cmd/led";

EthernetClient ethClient;
PubSubClient mqtt(ethClient);

unsigned long lastReconnect = 0;
unsigned long lastPublish = 0;

void onMessage(char* topic, byte* payload, unsigned int length) {
  Serial.print(F("<- "));
  Serial.print(topic);
  Serial.print(F(" = "));
  Serial.write(payload, length);
  Serial.println();

  if (strcmp(topic, T_CMD) == 0 && length > 0) {
    digitalWrite(LED_BUILTIN, payload[0] == '1' ? HIGH : LOW);
  }
}

bool connectMqtt() {
  Serial.print(F("MQTT connecting... "));
  // id, user, pass, willTopic, willQos, willRetain, willMessage
  if (mqtt.connect(CLIENT_ID, MQTT_USER, MQTT_PASS, T_STATUS, 1, true, "offline")) {
    Serial.println(F("connected"));
    mqtt.publish(T_STATUS, "online", true);
    mqtt.subscribe(T_CMD, 1);
    return true;
  }
  Serial.print(F("failed, rc="));
  Serial.println(mqtt.state());
  return false;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);           // SD kartı devre dışı bırak
  Serial.begin(9600);

  Ethernet.init(10);               // W5100/W5500 chip select
  Serial.println(F("DHCP..."));
  if (Ethernet.begin(mac) == 0) {
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println(F("No Ethernet shield found"));
    } else {
      Serial.println(F("DHCP failed - check the cable or use a static IP"));
    }
    while (true) delay(1000);
  }
  Serial.print(F("IP: "));
  Serial.println(Ethernet.localIP());

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(30);
}

void loop() {
  if (!mqtt.connected()) {
    unsigned long now = millis();
    if (now - lastReconnect > 5000) {   // bloklamadan 5 sn'de bir tekrar dene
      lastReconnect = now;
      connectMqtt();
    }
  } else {
    mqtt.loop();
  }

  if (mqtt.connected() && millis() - lastPublish > 10000) {
    lastPublish = millis();
    char buf[8];
    int raw = analogRead(A0);
    itoa(raw, buf, 10);
    mqtt.publish(T_TEMP, buf);
  }

  Ethernet.maintain();              // DHCP kirasını yenile
}

Bu sketch'teki bazı tercihler göründüğünden daha önemlidir:

  • F("...") string'leri sabit metni RAM yerine flash'ta tutar. Uno'da bir düzine debug string'i aksi hâlde belleğin dörtte birini yiyebilir.
  • Yeniden bağlanma bloklamaz. loop() denemeler arasında çalışmaya devam eder; böylece broker erişilemezken yerel mantık (butonlar, röleler, watchdog) donmaz.
  • mqtt.loop() sık çalışmalıdır. Gelen paketleri okur ve keep alive PINGREQ'ini gönderir. Kodunuzdaki uzun bir delay(), broker'ın bağlantıyı 1,5 × keep alive sonra kesmesine yol açar.
  • Bir Last Will ve retained bir online her dashboard'a güvenilir bir çevrimiçi/çevrimdışı göstergesi verir. Bkz. MQTT Last Will.

DHCP yerine statik IP

Endüstriyel ağlarda çoğu zaman DHCP yoktur. Adresi, DNS sunucusunu, gateway'i ve alt ağ maskesini açıkça verin; DNS olmadan broker.connectmq.com hostname'i çözümlenemez:

Static configuration
IPAddress ip(192, 168, 1, 50);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

Ethernet.begin(mac, ip, dns, gateway, subnet);

DNS yoksa mqtt.setServer() bir IPAddress de kabul eder; ancak bu durumda broker IP'sinin değişmesi tüm cihazları bozar. Ağ izin verdiği sürece hostname tercih edin.

Uçtan uca test

  1. Seri Monitörü 9600 baud ile açın. Bir IP adresi, ardından MQTT connecting... connected görmelisiniz.
  2. Online MQTT istemcisini açın, güvenli WebSocket ile broker.connectmq.com adresine bağlanın (port 8084, path /mqtt) ve testmqtt/arduino-eth/0001/# topic'ine subscribe olun.
  3. Retained status = online hemen gelir, ardından her on saniyede bir temp değeri.
  4. testmqtt/arduino-eth/0001/cmd/led topic'ine 1 yayınlayın. Karttaki LED yanar ve Seri Monitör mesajı yazdırır. Söndürmek için 0 yayınlayın.
  5. Ağ kablosunu çekin. Yaklaşık 45 saniye sonra broker will mesajını gönderir ve durum offline olur. Kabloyu geri takın: sketch birkaç saniye içinde yeniden bağlanır ve tekrar online yayınlar.

Arduino'yu online istemcide izleyin

Terminalden test etmeyi tercih ederseniz mosquitto_pub ve mosquitto_sub aynı işi görür: mosquitto_sub -h broker.connectmq.com -t 'testmqtt/arduino-eth/#' -v.

mqtt.state() değerini okuma

connect() başarısız olduğunda state() nedenini söyler. PubSubClient'ın tanımladığı kodlar şunlardır:

KodSabitAnlamı
-4MQTT_CONNECTION_TIMEOUTKeep alive süresi içinde yanıt yok
-3MQTT_CONNECTION_LOSTAğ bağlantısı koptu
-2MQTT_CONNECT_FAILEDTCP bağlantısı başarısız (DNS, IP, port, firewall)
-1MQTT_DISCONNECTEDDüzgün şekilde bağlantı kesildi
1MQTT_CONNECT_BAD_PROTOCOLBroker protokol sürümünü reddetti
2MQTT_CONNECT_BAD_CLIENT_IDClient ID reddedildi
3MQTT_CONNECT_UNAVAILABLEBroker kullanılamıyor
4MQTT_CONNECT_BAD_CREDENTIALSKullanıcı adı veya şifre yanlış
5MQTT_CONNECT_UNAUTHORIZEDYetkisiz

failed, rc=-2 açık ara en yaygın olanıdır: shield broker'a TCP üzerinden hiç ulaşamamıştır. Ethernet.localIP() değerinin 0.0.0.0 olmadığını, DNS sunucusunun erişilebilir olduğunu ve giden 1883 portuna izin verildiğini kontrol edin. PubSubClient sorun giderme rehberi her kodu ayrıntılı ele alıyor.

Bellek ve paket boyutu sınırları

PubSubClient gelen ve giden paketler için tek bir buffer ayırır. Varsayılan boyutu 256 bayttır ve buna sabit başlık, topic ve payload dahildir. Buffer'dan büyük bir paket alınırken sessizce atılır, gönderirken de publish() false döndürür.

Raising the buffer
mqtt.setBufferSize(512);   // setup() içinde, connect() öncesinde bir kez çağırın

Uno'da buffer'ı makul, topic'leri kısa tutun. Bir paketin tam boyutunu paket boyutu hesaplayıcı ile bulabilirsiniz. Mega'da 1–2 KB buffer'lar gerçekçidir. Büyük bir payload hâlâ başarısız oluyorsa paket çok büyük rehberi broker tarafındaki sınırları da anlatıyor.

Hesaba katmanız gereken diğer sınırlar:

  • Yalnızca QoS 0 publish. PubSubClient QoS 0 ile publish eder, QoS 0 veya 1 ile subscribe olur. Teslimat önemliyse retained bir durum topic'i veya uygulama seviyesinde bir onay kullanın.
  • Yalnızca MQTT 3.1.1. Reason code ve MQTT 5 property'leri yok.
  • Socket sayısı. W5100'de 4, W5500'de 8 socket vardır. DHCP ve DNS kısa süreliğine birini kullanır; aynı zamanda web sunucusu da çalıştıran bir sketch'te socket'ler tükenebilir.

Uno'nun ötesine ne zaman geçmeli

Uno + Ethernet kombinasyonu basit, kablolu, yerel cihazlar için mükemmeldir. TLS, daha büyük JSON payload'ları veya MQTT 5 özellikleri gerekiyorsa aynı PubSubClient kodu W5500'lü bir ESP32'ye ya da Wi-Fi kartlarına neredeyse hiç değişmeden taşınır: bkz. Arduino ve MQTT ile ESP32. AVR üzerinde QoS 1 ile publish etmek istiyorsanız arduino-mqtt kütüphanesine göz atmaya değer.

MQTT'deki kablolu bir Arduino büyük ölçüde disiplin meselesidir: benzersiz MAC ve client ID'ler, F() string'leri, bloklamayan bir yeniden bağlanma ve asla takılmayan bir döngü. Seri log'un connected demesini sağlayın, publish'i, komutları ve Last Will'i online istemciden kanıtlayın; kart yıllarca dokunulmadan çalışacaktır.

Sıkça sorulan sorular

Arduino Uno TLS üzerinden MQTT kullanabilir mi?

Pratikte hayır. ATmega328P'de 2 KB RAM var; bu bir TLS handshake için çok az ve W5100/W5500 çipleri TLS'i kendileri yapmaz. Güvenilir bir ağda şifresiz MQTT kullanın, TLS ile ileten yerel bir gateway koyun ya da ESP32 veya daha fazla belleği olan bir karta geçin.

PubSubClient neden uzun mesajlarımı almıyor?

PubSubClient, buffer'ından büyük her paketi atar; varsayılan buffer topic ve başlıklar dahil 256 bayttır. setBufferSize() ile daha büyük bir değer verin; ancak Uno'da her baytın 2 KB RAM'den gittiğini unutmayın.

PubSubClient QoS 1 ile publish yapabilir mi?

Hayır. PubSubClient yalnızca QoS 0 ile publish eder ve MQTT 3.1.1'i destekler. QoS 0 veya 1 ile subscribe olabilir. QoS 1 publish gerekiyorsa uygulama seviyesinde bir onay ekleyin veya başka bir kütüphane kullanın.