نحوه اتصال ESP-EYE به thnigsConnect

ESP-EYE یک برد توسعه برای تشخیص تصویر و پردازش صدا است که می‌تواند در برنامه‌های مختلف AIoT استفاده شود.
این برد شامل یک تراشه ESP32، یک دوربین 2 مگاپیکسل و یک میکروفون است. ESP-EYE دارای حافظه‌ای بسیار بزرگ است، با یک 8 مگابایت PSRAM و یک 4 مگابایت فلش.
همچنین این برد از طریق Wi-Fi انتقال تصویر را پشتیبانی می‌کند و از طریق پورت Micro-USB برای اشکال‌زدایی قابل استفاده است.

در این راهنما، خواهیم آموخت چگونه دستگاهی را در Thingsboard ایجاد کنیم، کتابخانه‌ها و ابزارهای مورد نیاز را نصب کنیم.
سپس کد خود را تغییر داده و به دستگاه بارگذاری کنیم و نتایج برنامه‌نویسی خود را بررسی کنیم و با استفاده از داشبورد وارد شده، داده‌های خود را در ThingsBoard بررسی کنیم. دستگاه ما با استفاده از قابلیت‌های درخواست‌های مشترک و ویژگی‌های مشترک، با ThingsBoard همگام می‌شود.
البته، با استفاده از قابلیت‌های ارائه شده مانند ویژگی‌های مشترک یا درخواست‌های RPC، دستگاه خود را کنترل خواهیم کرد.

پیشنیازها

برای ادامه راهنما، به موارد زیر نیاز داریم:

  • ESP-EYE
    محیط توسعه Arduino
  • حساب ThingsBoard

ایجاد دستگاه در ThingsBoard

برای سهولت، ما دستگاه را به صورت دستی با استفاده از رابط کاربری ایجاد می‌کنیم.

  • به نمونه ThingsBoard خود وارد شوید و به “موجودیت‌ها” بروید. سپس صفحه “دستگاه‌ها” را انتخاب کنید.
  • روی آیکون “+” در گوشه بالا سمت راست جدول کلیک کرده و سپس “اضافه کردن دستگاه جدید” را انتخاب کنید.
  • نام دستگاه را وارد کنید. به عنوان مثال، “دستگاه من”. در این زمان تغییرات دیگری لازم نیست. برای اضافه کردن دستگاه، روی “افزودن” کلیک کنید.
  • دستگاه شما اضافه شده است.

//img

نصب کتابخانه‌ها و ابزارهای مورد نیاز:
برای نصب برد در محیط توسعه Arduino IDE به مسیر File > Preferences بروید و URL زیر را به فیلد Additional Boards Manager URLs اضافه کنید.

https://dl.espressif.com/dl/package_esp32_index.json

//هImg

سپس به قسمت Tools > Board > Board Manager بروید و برد ESP32 توسط شرکت Espressif Systems را نصب کنید.

//img

بعد از تکمیل نصب، برد را از منوی Board انتخاب کنید:
Tools > Board > M5Stack > M5Stack-Timer-CAM.

همچنین، فراموش نکنید پورت مربوط به دستگاه را مشخص کنید:

Tools > Port > /dev/ttyUSB0.

پورت بستگی به سیستم عامل شما دارد و ممکن است متفاوت باشد:

  • برای لینوکس / مک، ممکن است /dev/ttyUSBX باشد
  • برای ویندوز – COMX.

که X – عددی است که توسط سیستم شما اختصاص داده شده است.

برای نصب ThingsBoard Arduino SDK، باید مراحل زیر را انجام دهید:

  • به بخش “ابزارها” بروید و بر روی “مدیریت کتابخانه‌ها” کلیک کنید.
  • کلمه “ThingsBoard” را در جعبه جستجو قرار داده و برای کتابخانه یافت شده بر روی دکمه “نصب” کلیک کنید.

//img

در این مرحله، تمام کتابخانه‌ها و ابزارهای مورد نیاز را نصب کرده‌ایم.

اتصال دستگاه به ThingsConnect

برای اتصال دستگاه به ThingsBoard، ابتدا باید اطلاعات احراز هویت دستگاه را دریافت کنید. ThingsBoard از انواع مختلف احراز هویت دستگاه پشتیبانی می‌کند. ما پیشنهاد می‌کنیم از مجوزهای پیش‌فرضی که به طور خودکار تولید می‌شوند استفاده کنید که یک توکن دسترسی است.

  • بر روی ردیف دستگاه در جدول کلیک کنید تا جزئیات دستگاه باز شود.
  • روی “کپی توکن دسترسی” کلیک کنید. توکن در کلیپ بورد شما کپی خواهد شد. لطفاً آن را در یک مکان امن ذخیره کنید.

//img

حالا زمان برنامه‌نویسی برد برای اتصال به ThingsBoard است. برای این کار می‌توانید از کد زیر استفاده کنید. این کد تمام عملکردهای مورد نیاز برای این راهنما را شامل می‌شود.

#include "battery.h"
#include "esp_camera.h"
#include <WiFi.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"

#define THINGSBOARD_ENABLE_DYNAMIC 1

#include <ThingsBoard.h>
#include <esp_heap_caps.h>

extern "C" {
#include "libb64/cencode.h"
}

constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID";
constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";

// See https://thingsboard.io/docs/getting-started-guides/helloworld/
// to understand how to obtain an access token
constexpr char TOKEN[] = "YOUR_ACCESS_TOKEN";

// Thingsboard we want to establish a connection too
constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io";
// MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port.
constexpr uint16_t THINGSBOARD_PORT = 1883U;

// Maximum size packets will ever be sent or received by the underlying MQTT client,
// if the size is to small messages might not be sent or received messages will be discarded
constexpr uint32_t MAX_MESSAGE_SIZE = 100U * 1024;

// Baud rate for the debugging serial connection.
// If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable
constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U;

// Definitions for camera pins
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21

// Initialize underlying client, used to establish a connection
WiFiClient wifiClient;
// Initialize ThingsBoard instance with the maximum needed buffer size
ThingsBoard tb(wifiClient, MAX_MESSAGE_SIZE);

// Attribute names for attribute request and attribute updates functionality

constexpr char BLINKING_INTERVAL_ATTR[] = "blinkingInterval";
constexpr char LED_MODE_ATTR[] = "ledMode";
constexpr char LED_STATE_ATTR[] = "ledState";
constexpr char PICTURE_ATTR[] = "photo";

// Statuses for subscribing to rpc
bool subscribed = false;

// handle led state and mode changes
volatile bool attributesChanged = false;

// LED modes: 0 - continious state, 1 - blinking
volatile int ledMode = 0;

// Current led state
volatile bool ledState = false;

// Settings for interval in blinking mode
constexpr uint16_t BLINKING_INTERVAL_MS_MIN = 10U;
constexpr uint16_t BLINKING_INTERVAL_MS_MAX = 60000U;
volatile uint16_t blinkingInterval = 1000U;

uint32_t previousStateChange;

// For telemetry
constexpr int16_t telemetrySendInterval = 2000U;
uint32_t previousDataSend;

// Picture buffer
char *imageBuffer;

// Flag to send a picture
volatile bool sendPicture = false;

// List of shared attributes for subscribing to their updates
constexpr std::array<const char *, 2U> SHARED_ATTRIBUTES_LIST = {
  LED_STATE_ATTR,
  BLINKING_INTERVAL_ATTR
};

// List of client attributes for requesting them (Using to initialize device states)
constexpr std::array<const char *, 1U> CLIENT_ATTRIBUTES_LIST = {
  LED_MODE_ATTR
};

/// @brief Initalizes WiFi connection,
// will endlessly delay until a connection has been successfully established
void InitWiFi() {
  Serial.println("Connecting to AP ...");
  // Attempting to establish a connection to the given WiFi network
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    // Delay 500ms until a connection has been succesfully established
    delay(500);
    Serial.println(WiFi.status());
    Serial.println(WL_CONNECTED);
    Serial.println(".");
  }
  Serial.println("Connected to AP");
}

/// @brief Reconnects the WiFi uses InitWiFi if the connection has been removed
/// @return Returns true as soon as a connection has been established again
const bool reconnect() {
  if (WiFi.status() == WL_CONNECTED) {
    return true;
  }

  // If we aren't establish a new connection to the given WiFi network
  InitWiFi();
  return true;
}


bool initCamera() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  config.frame_size = FRAMESIZE_240X240;
  config.jpeg_quality = 10;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return false;
  }

  sensor_t *s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  s->set_vflip(s, 1);        // flip it back
  s->set_brightness(s, 1);   // up the brightness just a bit
  s->set_saturation(s, -2);  // lower the saturation

  return true;
}


bool captureImage() {
  camera_fb_t *fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb) {
    return false;
  }
  encode((uint8_t *)fb->buf, fb->len);
  esp_camera_fb_return(fb);
  return true;
}


void encode(const uint8_t *data, size_t length) {
  size_t size = base64_encode_expected_len(length) + 1;
  base64_encodestate _state;
  base64_init_encodestate(&_state);
  int len = base64_encode_block((char *)&data[0], length, &imageBuffer[0], &_state);
  len = base64_encode_blockend((imageBuffer + len), &_state);
}


/// @brief Processes function for RPC call "setLedMode"
/// RPC_Data is a JSON variant, that can be queried using operator[]
/// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details
/// @param data Data containing the rpc data that was called and its current value
/// @return Response that should be sent to the cloud. Useful for getMethods
RPC_Response processSetLedMode(const RPC_Data &data) {
  Serial.println("Received the set led state RPC method");

  // Process data
  int new_mode = data;

  Serial.print("Mode to change: ");
  Serial.println(new_mode);

  if (new_mode != 0 && new_mode != 1) {
    return RPC_Response("error", "Unknown mode!");
  }

  ledMode = new_mode;

  attributesChanged = true;

  // Returning current mode
  return RPC_Response("newMode", (int)ledMode);
}


/// @brief Processes function for RPC call "setLedMode"
/// RPC_Data is a JSON variant, that can be queried using operator[]
/// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details
/// @param data Data containing the rpc data that was called and its current value
/// @return Response that should be sent to the cloud. Useful for getMethods
RPC_Response processTakePicture(const RPC_Data &data) {
  Serial.println("Received the take picture RPC method");

  if (!captureImage()) {
    return RPC_Response("error", "Cannot take a picture!");
  }

  sendPicture = true;

  // Returning current mode
  return RPC_Response("size", strlen(imageBuffer));
}


// Optional, keep subscribed shared attributes empty instead,
// and the callback will be called for every shared attribute changed on the device,
// instead of only the one that were entered instead
const std::array<RPC_Callback, 2U> callbacks = {
  RPC_Callback{ "setLedMode", processSetLedMode },
  RPC_Callback{ "takePicture", processTakePicture }
};


/// @brief Update callback that will be called as soon as one of the provided shared attributes changes value,
/// if none are provided we subscribe to any shared attribute change instead
/// @param data Data containing the shared attributes that were changed and their current value
void processSharedAttributes(const Shared_Attribute_Data &data) {
  for (auto it = data.begin(); it != data.end(); ++it) {
    if (strcmp(it->key().c_str(), BLINKING_INTERVAL_ATTR) == 0) {
      const uint16_t new_interval = it->value().as<uint16_t>();
      if (new_interval >= BLINKING_INTERVAL_MS_MIN && new_interval <= BLINKING_INTERVAL_MS_MAX) {
        blinkingInterval = new_interval;
        Serial.print("Updated blinking interval to: ");
        Serial.println(new_interval);
      }
    } else if (strcmp(it->key().c_str(), LED_STATE_ATTR) == 0) {
      ledState = it->value().as<bool>();
      digitalWrite(LED_BUILTIN, ledState ? HIGH : LOW);
      Serial.print("Updated state to: ");
      Serial.println(ledState);
    }
  }
  attributesChanged = true;
}

void processClientAttributes(const Shared_Attribute_Data &data) {
  for (auto it = data.begin(); it != data.end(); ++it) {
    if (strcmp(it->key().c_str(), LED_MODE_ATTR) == 0) {
      const uint16_t new_mode = it->value().as<uint16_t>();
      ledMode = new_mode;
    }
  }
}

const Shared_Attribute_Callback attributes_callback(SHARED_ATTRIBUTES_LIST.cbegin(), SHARED_ATTRIBUTES_LIST.cend(), &processSharedAttributes);
const Attribute_Request_Callback attribute_shared_request_callback(SHARED_ATTRIBUTES_LIST.cbegin(), SHARED_ATTRIBUTES_LIST.cend(), &processSharedAttributes);
const Attribute_Request_Callback attribute_client_request_callback(CLIENT_ATTRIBUTES_LIST.cbegin(), CLIENT_ATTRIBUTES_LIST.cend(), &processClientAttributes);

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);  // disable   detector
  bat_init();
  bat_hold_output();
  ledcAttachPin(4, 4);
  ledcSetup(4, 5000, 8);
  // Initalize serial connection for debugging
  imageBuffer = (char *)ps_malloc(50U * 1024);
  Serial.begin(SERIAL_DEBUG_BAUD);
  Serial.println("Camera initialization...");
  if (!initCamera()) {
    Serial.println("Camera initialization failed!");
    ESP.restart();
  }

  pinMode(LED_BUILTIN, OUTPUT);
  delay(1000);
  InitWiFi();
}

void loop() {
  delay(10);

  if (!reconnect()) {
    subscribed = false;
    return;
  }

  if (!tb.connected()) {
    subscribed = false;
    // Connect to the ThingsBoard
    Serial.print("Connecting to: ");
    Serial.print(THINGSBOARD_SERVER);
    Serial.print(" with token ");
    Serial.println(TOKEN);
    if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) {
      Serial.println("Failed to connect");
      return;
    }
    // Sending a MAC address as an attribute
    tb.sendAttributeString("macAddress", WiFi.macAddress().c_str());
  }

  if (!subscribed) {
    Serial.println("Subscribing for RPC...");
    // Perform a subscription. All consequent data processing will happen in
    // processSetLedState() and processSetLedMode() functions,
    // as denoted by callbacks array.
    if (!tb.RPC_Subscribe(callbacks.cbegin(), callbacks.cend())) {
      Serial.println("Failed to subscribe for RPC");
      return;
    }

    if (!tb.Shared_Attributes_Subscribe(attributes_callback)) {
      Serial.println("Failed to subscribe for shared attribute updates");
      return;
    }

    Serial.println("Subscribe done");
    subscribed = true;

    // Request current states of shared attributes
    if (!tb.Shared_Attributes_Request(attribute_shared_request_callback)) {
      Serial.println("Failed to request for shared attributes");
      return;
    }

    // Request current states of client attributes
    if (!tb.Client_Attributes_Request(attribute_client_request_callback)) {
      Serial.println("Failed to request for client attributes");
      return;
    }
  }

  if (sendPicture) {
    tb.sendTelemetryString(PICTURE_ATTR, imageBuffer);
    sendPicture = false;
  }

  if (attributesChanged) {
    attributesChanged = false;
    if (ledMode == 0) {
      previousStateChange = millis();
    }
    tb.sendTelemetryInt(LED_MODE_ATTR, ledMode);
    tb.sendTelemetryBool(LED_STATE_ATTR, ledState);
    tb.sendAttributeInt(LED_MODE_ATTR, ledMode);
    tb.sendAttributeBool(LED_STATE_ATTR, ledState);
  }

  if (ledMode == 1 && millis() - previousStateChange > blinkingInterval) {
    previousStateChange = millis();
    ledState = !ledState;
    digitalWrite(LED_BUILTIN, ledState);
    tb.sendTelemetryBool(LED_STATE_ATTR, ledState);
    tb.sendAttributeBool(LED_STATE_ATTR, ledState);
    if (LED_BUILTIN == 99) {
      Serial.print("LED state changed to: ");
      Serial.println(ledState);
    }
  }

  // Sending telemetry every telemetrySendInterval time
  if (millis() - previousDataSend > telemetrySendInterval) {
    previousDataSend = millis();
    tb.sendTelemetryInt("temperature", random(10, 20));
    tb.sendAttributeInt("rssi", WiFi.RSSI());
    tb.sendAttributeInt("channel", WiFi.channel());
    tb.sendAttributeString("ssid", WIFI_SSID);
    tb.sendAttributeString("localIp", WiFi.localIP().toString().c_str());
  }

  tb.loop();
}

در کد، جایگزین کردن عبارات کلیدی با نام شبکه WiFi SSID، رمز عبور، و توکن دسترسی دستگاه ThingsBoard لازم است.

متغیرهای مورد نیاز برای اتصال عبارتند از:

///جدول

...

constexpr char WIFI_SSID[] = "YOUR_WIFI_SSID";
constexpr char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";

constexpr char TOKEN[] = "YOUR_ACCESS_TOKEN";

constexpr char THINGSBOARD_SERVER[] = "demo.thingsboard.io";
constexpr uint16_t THINGSBOARD_PORT = 1883U;

constexpr uint32_t MAX_MESSAGE_SIZE = 256U;
constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U;

...

بخش ارسال داده (به طور پیش فرض، نمونه مقدار تصادفی برای کلید دما و برخی اطلاعات WiFi را ارسال می‌کند):

...
    tb.sendTelemetryInt("temperature", random(10, 20));
    tb.sendAttributeInt("rssi", WiFi.RSSI());
    tb.sendAttributeString("bssid", WiFi.BSSIDstr().c_str());
    tb.sendAttributeString("localIp", WiFi.localIP().toString().c_str());
    tb.sendAttributeString("ssid", WiFi.SSID().c_str());
    tb.sendAttributeInt("channel", WiFi.channel());
...

سپس با فشار دادن دکمه “آپلود” یا ترکیب کلیدهای Ctrl+U، کد را به دستگاه بارگذاری کنید.

//img

برای بررسی داده‌ها و امکان ارسال دستور یا داده به دستگاه، ما نیازمند ایجاد یک داشبورد هستیم.

ابتدا فایل داشبورد بررسی و کنترل داده دستگاه را دانلود کنید.

برای افزودن داشبورد به ThingsBoard، ما باید آن را وارد کنیم و برای انجام این کار، باید مراحل زیر را طی کنیم:

  • از منوی اصلی در سمت چپ صفحه، به بخش “داشبوردها” بروید.
  • روی دکمه “+” در گوشه بالا و سمت راست صفحه کلیک کنید و “وارد کردن داشبورد” را انتخاب کنید.
  • فایل dashboard.json خود را انتخاب کرده و دکمه وارد کردن را فشار دهید.
  • اکنون می‌توانید داشبورد وارد شده را در جدول مشاهده کنید.

///img

پس از وارد کردن، باید برای دستگاه خود نام مستعار (Alias) انتخاب کنیم.
برای این کار، باید روی آیکون قلم مورد نظر کلیک کرده و “مستعارهای entity” را انتخاب کنید، سپس مستعار “دستگاه من” را انتخاب کرده و با فشار دادن آیکون قلم، آن را برای ویرایش باز کنید.
سپس یک دستگاه با نام “دستگاه من” را از لیست کشویی انتخاب کنید و مستعار entity را ذخیره کنید. حالا باید قادر باشید داده‌ها را از دستگاه مشاهده کنید.

برای بررسی داده‌ها از دستگاه خود، باید داشبورد وارد شده را باز کنید:

  • با کلیک بر روی داشبورد در جدول، آن را باز کنید.
  • مشاهده‌ی داشبورد برای بررسی داده و کنترل دستگاه.
  • دریافت ویژگی‌ها (attributes) از دستگاه.
  • اطلاعات دستگاه از سرور ThingsBoard.
  • ابزارک برای مشاهده تاریخچه تغییرات حالت LED.
  • ابزارک برای مشاهده تاریخچه دمای شبیه‌سازی شده.

//img

همگام‌سازی وضعیت دستگاه با استفاده از درخواست‌های کلاینت و ویژگی‌های مشترک
برای دریافت وضعیت دستگاه از ThingsBoard در زمان راه‌اندازی، قابلیتی برای انجام این کار در کد وجود دارد.
بخش‌های مسئول کد نمونه:

  • تماس‌های بازگشت ویژگی‌ها (Attribute callbacks):
...
void processSharedAttributes(const Shared_Attribute_Data &data) {
  for (auto it = data.begin(); it != data.end(); ++it) {
    if (strcmp(it->key().c_str(), BLINKING_INTERVAL_ATTR) == 0) {
      const uint16_t new_interval = it->value().as<uint16_t>();
      if (new_interval >= BLINKING_INTERVAL_MS_MIN && new_interval <= BLINKING_INTERVAL_MS_MAX) {
        blinkingInterval = new_interval;
        Serial.print("Updated blinking interval to: ");
        Serial.println(new_interval);
      }
    } else if(strcmp(it->key().c_str(), LED_STATE_ATTR) == 0) {
      ledState = it->value().as<bool>();
      digitalWrite(LED_BUILTIN, ledState ? HIGH : LOW);
      Serial.print("Updated state to: ");
      Serial.println(ledState);
    }
  }
  attributesChanged = true;
}

void processClientAttributes(const Shared_Attribute_Data &data) {
  for (auto it = data.begin(); it != data.end(); ++it) {
    if (strcmp(it->key().c_str(), LED_MODE_ATTR) == 0) {
      const uint16_t new_mode = it->value().as<uint16_t>();
      ledMode = new_mode;
    }
  }
}
...
const Attribute_Request_Callback attribute_shared_request_callback(SHARED_ATTRIBUTES_LIST.cbegin(), SHARED_ATTRIBUTES_LIST.cend(), &processSharedAttributes);
const Attribute_Request_Callback attribute_client_request_callback(CLIENT_ATTRIBUTES_LIST.cbegin(), CLIENT_ATTRIBUTES_LIST.cend(), &processClientAttributes);
...

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

  • درخواست‌های ویژگی:
...
  // Request current states of shared attributes
  if (!tb.Shared_Attributes_Request(attribute_shared_request_callback)) {
    Serial.println("Failed to request for shared attributes");
    return;
  }

  // Request current states of client attributes
  if (!tb.Client_Attributes_Request(attribute_client_request_callback)) {
    Serial.println("Failed to request for client attributes");
    return;
  }
...

برای اینکه تماس‌های بازگشتی ما داده را دریافت کنند، باید یک درخواست به ThingsBoard ارسال کنیم.

کنترل دستگاه با استفاده از ویژگی های مشترک

با استفاده از ویژگی‌های مشترک، می‌توانیم دستگاه را کنترل کنیم.

همچنین، می‌توانیم با استفاده از قابلیت به‌روزرسانی ویژگی مشترک، دوره چشمک زدن را تغییر دهیم.

  • برای تغییر دوره چشمک زدن، فقط کافیست مقدار را در داشبورد خود تغییر دهید.
  • بعد از اعمال تغییرات با فشار دادن علامت تیک، پیامی تأییدیه مشاهده خواهید کرد.

//هimg

برای تغییر وضعیت وقتی که چشمک زدن غیرفعال است، می‌توانیم از سوئیچ در همان ویجت استفاده کنیم:

  • این کار فقط زمانی امکان پذیر است که حالت چشمک زدن غیرفعال باشد.

//img

برای دستیابی به این هدف، متغیری به نام “blinkingInterval” در بخش‌های زیر از کد استفاده شده است:

  • کالبک برای به‌روزرسانی ویژگی‌های مشترک:
...

void processSharedAttributes(const Shared_Attribute_Data &data) {
  for (auto it = data.begin(); it != data.end(); ++it) {
    if (strcmp(it->key().c_str(), BLINKING_INTERVAL_ATTR) == 0) {
      const uint16_t new_interval = it->value().as<uint16_t>();
      if (new_interval >= BLINKING_INTERVAL_MS_MIN && new_interval <= BLINKING_INTERVAL_MS_MAX) {
        blinkingInterval = new_interval;
        Serial.print("Updated blinking interval to: ");
        Serial.println(new_interval);
      }
    } else if(strcmp(it->key().c_str(), LED_STATE_ATTR) == 0) {
      ledState = it->value().as<bool>();
      digitalWrite(LED_BUILTIN, ledState ? HIGH : LOW);
      Serial.print("Updated state to: ");
      Serial.println(ledState);
    }
  }
  attributesChanged = true;
}

...
const Shared_Attribute_Callback attributes_callback(SHARED_ATTRIBUTES_LIST.cbegin(), SHARED_ATTRIBUTES_LIST.cend(), &processSharedAttributes);
...
  • اشتراک برای به‌روزرسانی ویژگی‌های مشترک:
...
    if (!tb.Shared_Attributes_Request(attribute_shared_request_callback)) {
      Serial.println("Failed to request for shared attributes");
      return;
    }
...
  • بخشی از کد برای چشمک زدن:
...

  if (ledMode == 1 && millis() - previousStateChange > blinkingInterval) {
    previousStateChange = millis();
    ledState = !ledState;
    digitalWrite(LED_BUILTIN, ledState);
    tb.sendTelemetryBool(LED_STATE_ATTR, ledState);
    tb.sendAttributeBool(LED_STATE_ATTR, ledState);
    if (LED_BUILTIN == 99) {
      Serial.print("LED state changed to: ");
      Serial.println(ledState);
    }
  }
...

شما می‌توانید منطق را تغییر دهید تا به اهداف خود برسید و پردازشی برای ویژگی‌هایتان اضافه کنید.

کنترل دستگاه با استفاده از RPC

شما می‌توانید به صورت دستی وضعیت LED را تغییر داده و حالت آن را بین روشنایی مداوم و چشمک‌زنی تغییر دهید. برای این کار، می‌توانید از بخش‌های زیر در داشبورد ما استفاده کنید:

  • استفاده از ویجت سوئیچ برای تغییر وضعیت LED به روشنایی مداوم.
  • استفاده از ویجت سوئیچ گرد برای تغییر وضعیت LED به حالت چشمک‌زنی.

///Img

لطفاً توجه کنید که شما تنها می‌توانید وضعیت LED را تغییر دهید در صورتی که حالت چشمک‌زنی غیرفعال باشد.

در نمونه کد، قابلیت بررسی دستورات RPC وجود دارد.
برای دسترسی به کنترل دستگاه، از بخش‌های زیر در کد استفاده کرده‌ایم:

  • کالبک برای درخواست‌های RPC:
...

RPC_Response processSetLedMode(const RPC_Data &data) {
  Serial.println("Received the set led state RPC method");

  // Process data
  int new_mode = data;

  Serial.print("Mode to change: ");
  Serial.println(new_mode);

  if (new_mode != 0 && new_mode != 1) {
    return RPC_Response("error", "Unknown mode!");
  }

  ledMode = new_mode;

  attributesChanged = true;

  // Returning current mode
  return RPC_Response("newMode", (int)ledMode);
}

...

const std::array<RPC_Callback, 2U> callbacks = {
  RPC_Callback{ "setLedMode", processSetLedMode },
  RPC_Callback{ "takePicture", processTakePicture }
};

...
  • اشتراک برای درخواست های RPC:
...
    if (!tb.RPC_Subscribe(callbacks.cbegin(), callbacks.cend())) {
      Serial.println("Failed to subscribe for RPC");
      return;
    }
...

مانند اینکه برد شامل دوربین است، ما می‌توانیم یک عکس بگیریم و آن را در داشبورد مشاهده کنیم.

  • می‌توانید با فشار دادن دکمه روی داشبورد ThingsBoard، تصویری از ماژول دوربین بگیرید و آن را مشاهده کنید.

//img

برای گرفتن عکس، ما دستور “takePicture” را به دستگاه ارسال می‌کنیم.

بخش زیر از کد، یک عکس می‌گیرد.

...

bool captureImage() {
  camera_fb_t *fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb) {
    return false;
  }
  encode((uint8_t *)fb->buf, fb->len);
  esp_camera_fb_return(fb);
  return true;
}
...

به دلیل عدم امکان ارسال یک آرایه بایت از تصویر به صورت مستقیم در JSON، ما همچنین بایت‌ها را به صورت Base64 رمزگذاری می‌کنیم:

...
void encode(const uint8_t *data, size_t length) {
  size_t size = base64_encode_expected_len(length) + 1;
  base64_encodestate _state;
  base64_init_encodestate(&_state);
  int len = base64_encode_block((char *)&data[0], length, &imageBuffer[0], &_state);
  len = base64_encode_blockend((imageBuffer + len), &_state);
}
...

تصویر رمزگذاری شده ما در حلقه اصلی ارسال خواهد شد.

...
if (sendPicture) {
tb.sendTelemetryString(PICTURE_ATTR, imageBuffer);
sendPicture = false;
}
...

شما می‌توانید کد را تغییر دهید تا به اهداف خود برسید و پردازش برای دستورات RPC خود اضافه کنید.

نتیجه‌گیری

با دانشی که در این راهنما شرح داده شده است، به راحتی می‌توانید ESP-EYE خود را متصل کرده و داده‌ها را به ThingsBoard ارسال کنید.

برای یادگیری بیشتر درباره مفاهیم و ویژگی‌های کلیدی، به مستندات پلتفرم مراجعه کنید. به عنوان مثال، قوانین هشدار را پیکربندی کنید یا داشبوردها را سفارشی کنید.

عناوین هر بخش