آموزش BLE با ESP32 و ساخت پکت‌های ارتباطی
Bluetooth Low Energy

آموزش BLE، پکت‌ها و پروتکل با مثال ESP32

در این مقاله BLE را از نگاه مهندسی بررسی می‌کنیم: نقش central و peripheral، advertising، GATT، سرویس‌ها، characteristicها، روش‌های read/write/notify و طراحی پکت‌های سبک برای پروژه‌های واقعی ESP32.

BLE چیست و چرا برای ESP32 مهم است؟

BLE یا Bluetooth Low Energy نسخه کم‌مصرف بلوتوث است که برای تبادل داده‌های کوتاه، سنسورها، تجهیزات پوشیدنی، تنظیمات محلی دستگاه و کنترل نزدیک طراحی شده است. در ESP32، BLE معمولا زمانی انتخاب خوبی است که بخواهیم بدون Wi-Fi و بدون مصرف زیاد انرژی، موبایل را به برد متصل کنیم.

برخلاف بلوتوث کلاسیک که برای صدا و جریان داده پیوسته مناسب‌تر است، BLE ارتباط را به رویدادهای کوتاه و قابل کنترل تقسیم می‌کند. همین ویژگی باعث می‌شود برای ارسال وضعیت سنسور، فرمان روشن و خاموش، تغییر تنظیمات، یا خواندن مقدارهای دوره‌ای عالی باشد.

لایه‌های BLE از radio تا GATT
ساختار ساده‌شده BLE؛ از لایه فیزیکی تا GATT که بیشتر برنامه‌نویس‌ها با آن کار می‌کنند.
نمای مفهومی پکت‌های BLE
هر ارتباط BLE از پکت‌های کوچک تشکیل می‌شود؛ طراحی payload کوتاه و قابل parse کلید پایداری است.

نقش‌های اصلی در BLE

Advertising و پکت‌های تبلیغاتی

قبل از اتصال، peripheral بسته‌های advertising ارسال می‌کند تا central بتواند آن را پیدا کند. این بسته‌ها کوچک هستند و معمولا شامل نام دستگاه، UUID سرویس، manufacturer data یا چند flag ساده می‌شوند.

فیلدکاربردنکته طراحی
Device Nameنمایش نام دستگاه در اسکن موبایلکوتاه و قابل تشخیص باشد.
Service UUIDمعرفی سرویس اصلی دستگاهبرای فیلتر کردن سریع در اپ موبایل مفید است.
Manufacturer Dataارسال چند بایت اختصاصیبرای نسخه سخت‌افزار یا وضعیت کوتاه مناسب است.

GATT، سرویس و Characteristic

بعد از اتصال، بیشتر کار ما با GATT انجام می‌شود. یک سرویس مجموعه‌ای از characteristicهاست. هر characteristic یک مقدار دارد و می‌تواند قابلیت read، write، notify یا indicate داشته باشد.

برای مثال اگر ESP32 یک سنسور دما باشد، می‌توان یک سرویس اختصاصی ساخت و داخل آن characteristic مقدار دما را قرار داد. موبایل آن characteristic را می‌خواند یا برای notification مشترک می‌شود.

مثال ESP32: ساخت BLE Server ساده

نمونه زیر با Arduino Core برای ESP32 نوشته شده است. برد مثل یک peripheral دیده می‌شود، یک سرویس می‌سازد و مقدار characteristic را هر دو ثانیه با notification ارسال می‌کند.

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#define SERVICE_UUID "8f4a0001-3b7f-4b9a-9a52-1c7e10100001"
#define SENSOR_UUID  "8f4a0002-3b7f-4b9a-9a52-1c7e10100002"

BLECharacteristic *sensorChar;
bool deviceConnected = false;

class ServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *server) override {
    deviceConnected = true;
  }

  void onDisconnect(BLEServer *server) override {
    deviceConnected = false;
    server->getAdvertising()->start();
  }
};

void setup() {
  Serial.begin(115200);
  BLEDevice::init("LCENJ-ESP32-BLE");

  BLEServer *server = BLEDevice::createServer();
  server->setCallbacks(new ServerCallbacks());

  BLEService *service = server->createService(SERVICE_UUID);
  sensorChar = service->createCharacteristic(
    SENSOR_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
  );
  sensorChar->addDescriptor(new BLE2902());
  sensorChar->setValue("{\"counter\":0}");

  service->start();
  BLEAdvertising *advertising = server->getAdvertising();
  advertising->addServiceUUID(SERVICE_UUID);
  advertising->setScanResponse(true);
  advertising->start();
}

void loop() {
  static uint32_t counter = 0;
  String payload = "{\"counter\":" + String(counter++) + "}";
  sensorChar->setValue(payload.c_str());
  if (deviceConnected) {
    sensorChar->notify();
  }
  delay(2000);
}

Read، Write و Notify چه تفاوتی دارند؟

مثال ESP32: دریافت دستور از موبایل

در این نمونه یک characteristic قابل نوشتن داریم. اگر موبایل مقدار LED:ON یا LED:OFF بفرستد، ESP32 خروجی را تغییر می‌دهد.

#define CONTROL_UUID "8f4a0003-3b7f-4b9a-9a52-1c7e10100003"
#define LED_PIN 2

class ControlCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *c) override {
    String value = c->getValue().c_str();
    value.trim();
    if (value == "LED:ON") {
      digitalWrite(LED_PIN, HIGH);
    } else if (value == "LED:OFF") {
      digitalWrite(LED_PIN, LOW);
    }
    Serial.println("BLE command: " + value);
  }
};

// داخل setup بعد از ساخت service:
pinMode(LED_PIN, OUTPUT);
BLECharacteristic *controlChar = service->createCharacteristic(
  CONTROL_UUID,
  BLECharacteristic::PROPERTY_WRITE
);
controlChar->setCallbacks(new ControlCallbacks());

طراحی payload و پکت کاربردی

برای بیشتر پروژه‌های الکترونیکی، payload باید کوتاه، قابل تشخیص و قابل توسعه باشد. اگر داده ساده است، یک متن کوتاه مثل LED:ON کافی است. اگر چند فیلد دارید، JSON کوچک یا فرمت کلید-مقدار مناسب‌تر است.

{"type":"sensor","temp":27.4,"battery":83}

برای داده‌های پرتکرار، ارسال JSON طولانی می‌تواند باعث کاهش پایداری شود. در این حالت می‌توانید از پکت باینری کوتاه استفاده کنید؛ مثلا یک بایت نوع پیام، دو بایت مقدار سنسور و یک بایت checksum.

MTU، Connection Interval و سرعت واقعی

BLE برای payloadهای کوچک عالی است، اما برای فایل‌های بزرگ یا stream سریع مناسب نیست مگر با طراحی دقیق. سه پارامتر مهم روی سرعت و پایداری اثر دارند:

برای سنسورها، ارسال هر 500 میلی‌ثانیه تا چند ثانیه معمولا پایدارتر از ارسال پیوسته است. برای داده‌های چندبایتی، payload را کوتاه و قابل parse نگه دارید.

فایل نمونه کد ESP32

نمونه‌های کامل Arduino برای BLE Server، دریافت دستور LED و اسکن دستگاه‌ها داخل پکیج همین مقاله قرار دارد.

دانلود نمونه کدهای ESP32

نکات دیباگ BLE در پروژه‌های واقعی

جمع‌بندی

BLE در ESP32 بهترین انتخاب برای ارتباط کم‌مصرف، تنظیمات محلی، سنسورهای نزدیک و کنترل ساده با موبایل است. اگر advertising، نقش central/peripheral، سرویس‌های GATT و روش read/write/notify را درست طراحی کنید، ارتباط پایدار و قابل توسعه‌ای خواهید داشت.