اتصال مجدد پایدار MQTT در قطعی اینترنت
آموزش

اتصال مجدد پایدار MQTT در قطعی اینترنت

۱۰ آبان ۱۴۰۵۷ دقیقه مطالعه

اینترنت قطع می‌شود و بروکر گاهی به‌روزرسانی می‌شود. کد اتصال مجدد بدون توقف با فاصله‌گذاری تدریجی برای ESP32، پایتون و Node.js بنویسید.

قطعی اتفاق می‌افتد#

هر دستگاه متصل به اینترنت دیر یا زود قطع می‌شود: مودم ریست می‌شود، وای‌فای ضعیف است، اینترنت قطع می‌شود، یا بروکر برای نگهداری و به‌روزرسانی چند لحظه راه‌اندازی مجدد می‌شود. سؤال این نیست که قطع می‌شود یا نه؛ این است که دستگاه بعد از آن خودش و بدون دخالت شما برمی‌گردد یا نه. چند قاعده ساده این را تضمین می‌کند.

چهار قاعده#

  • برنامه را متوقف نکنید: حلقه‌ای که تا وصل شدن صبر می‌کند، کارهای محلی دستگاه (خواندن سنسور، کنترل رله، دکمه‌ها) را هم متوقف می‌کند.
  • فاصله را تدریجی زیاد کنید: بعد از هر تلاش ناموفق کمی بیشتر صبر کنید (مثلاً ۲، ۴، ۸ ثانیه تا حداکثر ۲ دقیقه) و کمی عدد تصادفی اضافه کنید تا همه دستگاه‌ها هم‌زمان هجوم نیاورند.
  • رد شدن احراز هویت با قطع شبکه فرق دارد: اگر بروکر اتصال را با کد ۵ (غیرمجاز) رد کرد، تلاش پشت سر هم فایده ندارد. خیلی کمتر تلاش کنید و خطا را جایی نمایش دهید.
  • بعد از هر اتصال دوباره مشترک شوید: بسیاری از کتابخانه‌ها اشتراک‌ها را بعد از اتصال مجدد خودشان تکرار نمی‌کنند.

ESP32 و ESP8266: اتصال بدون توقف#

به جای حلقه while در نمونه‌های ساده، این تابع را در هر دور loop() صدا بزنید. اگر وصل نیست و زمان تلاش بعدی نرسیده، فوراً برمی‌گردد و بقیه برنامه کارش را می‌کند:

cpp
unsigned long lastAttempt = 0;
unsigned long waitMs = 0;
unsigned long backoffMs = 2000;          // grows up to 2 minutes

void maintainMqtt() {
  if (client.connected()) {
    client.loop();
    return;
  }
  if (WiFi.status() != WL_CONNECTED) return;      // WiFi reconnects on its own
  if (millis() - lastAttempt < waitMs) return;    // overflow-safe

  lastAttempt = millis();
  if (client.connect(CLIENT_ID, MQTT_USER, MQTT_PASS)) {
    backoffMs = 2000;
    waitMs = 0;
    client.subscribe("alice/esp32-01/cmd");       // subscriptions don't survive a reconnect
    return;
  }

  int rc = client.state();
  Serial.printf("MQTT connect failed, state=%d\n", rc);
  // 5 = not authorized: wrong credentials or client id, retrying fast won't help
  waitMs = (rc == 5 ? 300000UL : backoffMs) + random(0, 1000);
  backoffMs = min(backoffMs * 2, 120000UL);
}

void loop() {
  maintainMqtt();
  readSensorsAndControlRelays();  // keeps working while offline
}

client.connect() خودش تا برقراری یا شکست اتصال TCP و TLS چند ثانیه طول می‌کشد؛ اگر کاری دارید که حتی این مکث را تحمل نمی‌کند، آن را با تایمر یا وقفه (interrupt) انجام دهید.

پایتون با paho-mqtt#

paho خودش اتصال مجدد را انجام می‌دهد؛ کافی است فاصله را تنظیم کنید و اشتراک را در on_connect بگذارید تا بعد از هر اتصال تکرار شود:

python
import paho.mqtt.client as mqtt

USER = "alice"

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print("connect refused:", reason_code)
        return
    client.subscribe(f"{USER}/esp32-01/#", qos=1)

def on_disconnect(client, userdata, flags, reason_code, properties):
    print("disconnected:", reason_code)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="py-01")
client.username_pw_set(USER, "YOUR_PASSWORD")
client.tls_set()
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.reconnect_delay_set(min_delay=2, max_delay=120)

client.connect_async("mqtt.mqttcloud.ir", 8883, keepalive=60)
client.loop_forever(retry_first_connection=True)

با connect_async و retry_first_connection=True، اگر اینترنت هنگام اجرای اسکریپت قطع باشد، برنامه با خطا بسته نمی‌شود و تا برقراری اتصال تلاش می‌کند.

Node.js با mqtt.js#

mqtt.js هم خودش دوباره وصل می‌شود و اشتراک‌ها را هم دوباره برقرار می‌کند. فاصله پیش‌فرض یک ثانیه و ثابت است؛ برای دستگاه‌های زیاد آن را بیشتر کنید:

js
const client = mqtt.connect("mqtts://mqtt.mqttcloud.ir:8883", {
  clientId: "node-01",
  username: "alice",
  password: process.env.MQTT_PASSWORD,
  reconnectPeriod: 5000,   // ms between attempts
  connectTimeout: 30000,
});

client.on("reconnect", () => console.log("reconnecting…"));
client.on("offline", () => console.log("offline"));

داده‌ها در زمان قطعی#

اگر داده نباید گم شود، در زمان قطعی آن را روی دستگاه نگه دارید (در حافظه، یا روی فلش برای قطعی‌های طولانی) و بعد از اتصال به ترتیب بفرستید. زمان اندازه‌گیری را داخل پیام بگذارید تا گیرنده بداند داده مال کی است (قالب پیام پیشنهادی). برای اینکه بقیه سیستم از قطعی باخبر شوند هم Last Will را تنظیم کنید.

یک خطای رایج: اگر دستگاه هر چند ثانیه قطع و وصل می‌شود، احتمالاً دو دستگاه یا دو برنامه با یک Client ID به بروکر وصل‌اند و مدام همدیگر را بیرون می‌اندازند. صفحه «فعالیت» داشبورد این الگو را به‌خوبی نشان می‌دهد؛ جزئیات بیشتر در عیب‌یابی اتصال MQTT.

#MQTT#ESP32#اتصال_مجدد