Минимизируйте размер программы ESP32, подключающейся к Bluetooth-устройству и запрашивающей HTTP-сервер одновременно.

Я пытаюсь расширить это программа для платы ESP32, которая подключается к кубику Рубика по Bluetooth, чтобы теперь отправлять POST-запросы на сервер с данными кубика.

Я добавил

//**Makes http Server Post with Data**
  HTTPClient http;
  http.begin(F("http://mydomain/RubiksCube"));
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  int httpCode = http.POST("data=" + data);
  http.end();

в notifyCallback() для отправки сообщения при получении данных Bluetooth, и

WiFi.begin("[ssid]", "[password]");

в настройке()

Я столкнулся с проблемой, что мой измененный скетч использует 1522194 байта (116%) места для хранения программы. Максимум 1310720 байт. Есть ли способ оптимизировать программу, чтобы она подошла к используемой мной плате?

Исходный скетч использует 972 334 байта (74%) места для хранения программы. Я могу уменьшить свой скетч до 95%, удалив

 WiFi.begin("[ssid]", "[password]");

Я подозреваю, что есть способ сделать это (или что-то другое) в реализации более низкого уровня или, по крайней мере, удалить ненужные функции библиотеки. Есть ли какой-нибудь более упрощенный подход к этой задаче?

Вот весь модифицированный скетч

/**
 * ESP32-SmartCube
 * Copyright (c) 2019 Playful Technology
 * 
 * ESP32 sketch to connect to Xiaomi "Smart Magic Cube" Rubik's Cube via Bluetooth LE
 * and decode notification messages containing puzzle state.
 * Prints output to serial connection of the last move made, and when the cube is fully
 * solved, triggers a relay output
 */

// ВКЛЮЧАЕТ В СЕБЯ
// библиотека ESP32 для Bluetooth LE
#include "BLEDevice.h"

#include <WiFi.h>
#include <HTTPClient.h>

// КОНСТАНТЫ
// MAC-адрес кубика Рубика
// Это можно обнаружить, запустив сканирование ДО касания куба. Затем поверните любое лицо
// разбудить куб и посмотреть, какое новое устройство появится
static BLEAddress *pServerAddress = new BLEAddress("c4:a7:2f:2a:69:a3");
// Удаленный сервис, к которому мы хотим подключиться
static BLEUUID serviceUUID("0000aadb-0000-1000-8000-00805f9b34fb");
// Характеристики удаленного сервиса, который мы хотим отслеживать
static BLEUUID charUUID("0000aadc-0000-1000-8000-00805f9b34fb");
// Следующие константы используются для расшифровки данных, представляющих состояние куба
// см. https://github.com/cs0x7f/cstimer/blob/master/src/js/bluetooth.js
const uint8_t decryptionKey[] = {176, 81, 104, 224, 86, 137, 237, 119, 38, 26, 193, 161, 210, 126, 150, 81, 93, 13, 236, 249, 89, 235, 88, 24, 113, 81, 214, 131, 130, 199, 2, 169, 39, 165, 171, 41};
// На этом выводе будет отправлен ВЫСОКИЙ импульс, когда куб будет решен
const byte relayPin = 33;
// Это массив данных, представляющий решенный куб
const byte solution[16] = {0x12,0x34,0x56,0x78,0x33,0x33,0x33,0x33,0x12,0x34,0x56,0x78,0x9a,0xbc,0x00,0x00};

// ГЛОБАЛЬНЫЕ
// Нашли ли мы куб с правильным MAC-адресом для подключения?
static boolean deviceFound = false;
// Подключены ли мы сейчас к кубу?
static boolean connected = false;
// Свойства устройства, найденного при сканировании
static BLEAdvertisedDevice* myDevice;
// BT характеристика подключенного устройства
static BLERemoteCharacteristic* pRemoteCharacteristic;

// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
/**
 * Return the ith bit from an integer array
 */
int getBit(uint8_t* val, int i) {
  int n = ((i / 8) | 0);
  int shift = 7 - (i % 8);
  return (val[n] >> shift) & 1;    
}

/**
 * Return the ith nibble (half-byte, i.e. 16 possible values)
 */
uint8_t getNibble(uint8_t val[], int i) {
  if(i % 2 == 1) {
    return val[(i/2)|0] % 16;
  }
  return 0|(val[(i/2)|0] / 16);
}

// ВЫЗОВЫ
/**
 * Callbacks for devices found via a Bluetooth scan of advertised devices
 */
class AdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  // Обратный вызов onResult вызывается для каждого рекламируемого устройства, найденного при сканировании
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    // Выводим MAC-адрес этого устройства
    Serial.print(" - ");
    Serial.print(advertisedDevice.getAddress().toString().c_str());
    // Соответствует ли это устройство MAC-адресу, который мы ищем?
    if(advertisedDevice.getAddress().equals(*pServerAddress)) {
      // Остановить поиск других устройств
      advertisedDevice.getScan()->stop();
      // Создаем новое устройство на основе свойств рекламируемого устройства
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      // Установить флаг
      deviceFound = true;
      Serial.println(F(" - Connecting!"));
    }
    else {
      Serial.println(F("... MAC address does not match"));
    }
  }
};

/**
 * Callbacks for device we connect to
 */
class ClientCallbacks : public BLEClientCallbacks {
  // Вызывается при установке нового соединения
  void onConnect(BLEClient* pclient) {
    // цифровая запись (LED_BUILTIN, HIGH);
    connected = true;
  }
  // Вызывается при потере соединения
  void onDisconnect(BLEClient* pclient) {
    // цифровая запись (LED_BUILTIN, LOW);
    connected = false;
  }
};

/**
 * Called whenever a notication is received that the tracked BLE characterisic has changed
 */
static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
  // РАСШИФРОВАТЬ ДАННЫЕ
  // Ранние кубы Bluetooth использовали незашифрованный формат данных, который отправлял индексы угла/ребра как
  // простая необработанная строка, например, pData для решенного куба будет 12345678333333333123456789abc000041414141
  // Однако более новые кубы, например Giiker i3, шифруют данные с помощью циклического ключа, так что это же состояние может быть
  // 706f6936b1edd1b5e00264d099a4e8a19d3ea7f1 затем d9f67772c3e9a5ea6e84447abb527156f9dca705 и т.д.
  
  // Чтобы узнать, зашифрованы ли данные, мы сначала читаем предпоследний байт характеристических данных.
  // Как и в двух вышеприведенных примерах, если это 0xA7, мы знаем, что он зашифрован
  bool isEncrypted = (pData[18] == 0xA7);

  // Если он *зашифрован*...
  if(isEncrypted) {
    // Разделить последний байт на два 4-битных значения
    int offset1 = getNibble(pData, 38);
    int offset2 = getNibble(pData, 39);

    // Получить пару значений смещения из ключа дешифрования
    for (int i=0; i<20; i++) {
      // Применяем смещение к каждому значению в данных
      pData[i] += (decryptionKey[offset1 + i] + decryptionKey[offset2 + i]);
    }
  }

  // Первые 16 байт представляют состояние куба — 8 углов (с 3 ориентациями) и 12 ребер (можно перевернуть)
  String postData = "";
  Serial.print("Current State: ");
    for (int i=0; i<16; i++) {
      // Serial.print(pData[i], HEX);
      Serial.print(pData[i]);
      Serial.print(" ");
      postData.concat(pData[i]);
    }
  Serial.println("");

  //**Создает сообщение HTTP-сервера с данными**
  HTTPClient http;
  http.begin(F("http://mydomain/RubiksCube"));
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  int httpCode = http.POST("data=" + postData);
  http.end();

  // Байт 17 представляет последний сделанный поворот - первая половина байта - это лицо, а вторая половина байта - направление вращения
  int lastMoveFace = getNibble(pData, 32);
  int lastMoveDirection = getNibble(pData, 33);
  char* faceNames[6] = {"Front", "Bottom", "Right", "Top", "Left", "Back"};
  Serial.print("Last Move: ");
  Serial.print(faceNames[lastMoveFace-1]);
  Serial.print(lastMoveDirection == 1 ? " Face Clockwise" : " Face Anti-Clockwise" );
  Serial.println("");
  
  Serial.println("----");

  if(memcmp(pData, solution, 16) == 0) {
    Serial.println("Solved!");
    digitalWrite(relayPin, HIGH);
    delay(100);
    digitalWrite(relayPin, LOW);
  }
  
}

/*
 * Connect to the BLE server of the correct MAC address
 */
bool connectToServer() {
    Serial.print(F("Creating BLE client... "));
    BLEClient* pClient = BLEDevice::createClient();
    delay(500);
    Serial.println(F("Done."));

    Serial.print(F("Assigning callbacks... "));
    pClient->setClientCallbacks(new ClientCallbacks());
    delay(500);
    Serial.println(F(" - Done."));

    // Подключиться к удаляемому BLE-серверу.
    Serial.print(F("Connecting to "));
    Serial.print(myDevice->getAddress().toString().c_str());
    Serial.print(F("... "));
    pClient->connect(myDevice);
    delay(500);
    Serial.println(" - Done.");
    
    // Получаем ссылку на нужный нам сервис на удаленном BLE-сервере.
    Serial.print(F("Finding service "));
    Serial.print(serviceUUID.toString().c_str());
    Serial.print(F("... "));
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    delay(500);
    if (pRemoteService == nullptr) {
      Serial.println(F("FAILED."));
      return false;
    }
    Serial.println(" - Done.");
    delay(500);

    // Получить ссылку на характеристику в сервисе удаленного BLE-сервера.
    Serial.print(F("Finding characteristic "));
    Serial.print(charUUID.toString().c_str());
    Serial.print(F("... "));
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.println(F("FAILED."));
      return false;
    }
    Serial.println(" - Done.");
    delay(500);
    
    Serial.print(F("Registering for notifications... "));
    if(pRemoteCharacteristic->canNotify()) {
      pRemoteCharacteristic->registerForNotify(notifyCallback);
      Serial.println(" - Done.");
    }
    else {
      Serial.println(F("FAILED."));
      return false;
    }

    Serial.println("READY!");
}

/**
 * Search for any advertised devices
 */
void scanForDevices(){
  Serial.println("Scanning for Bluetooth devices...");
  // Получить сканер и установить обратный вызов, который мы хотим использовать, чтобы получать информацию, когда мы
  // обнаружено новое устройство. Указываем, что хотим активное сканирование и запускаем
  // сканирование для запуска в течение 30 секунд.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(30);
}

// Начальная настройка
void setup() {
  // Запускаем последовательное соединение, чтобы иметь возможность отслеживать данные отладки
  Serial.begin(115200);
  
  Serial.print("Initialising BLE...");
  BLEDevice::init("");
  delay(500);
  Serial.println(F("Done."));

  WiFi.begin("[ssid]", "[password]");

  // relayPin будет установлен ВЫСОКИМ, когда куб будет собран
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);

  // ledPin будет установлен в HIGH при подключении куба
  // pinMode(LED_BUILTIN, OUTPUT);
  Serial.println("Connected");
}

// Основная функция цикла программы
void loop() {
  // Если куб найден, подключаемся к нему
  if (deviceFound) {
    if(!connected) {
      connectToServer();
    }
  }
  else {
    scanForDevices();
  }
  // Добавим небольшую задержку
  delay(1000);
}

, 👍0

Обсуждение

Сколько флэш-памяти у вашего ESP32? Скорее всего это 4Мб, может хватило бы поменять схему разделов. Какую схему разделов вы выбрали?, @chrisl

Я не уверен, сколько flash у меня есть и как проверить. Я пытался загрузить скетч с размером флэш-памяти 4 МБ и схемой разделов по умолчанию 4 МБ с помощью spiffs., @Jack-Penn

Вы не используете SPIFFS, поэтому выберите схему разделов без него., @chrisl