#include "BLE_UpdateService.h" #include "WiFi.h" #include "my_wifi.h" #include "global.h" #include "AppUpgrade.h" #include "AppVersion.h" #include "BleSettings.h" #include "version.h" static const char *tag = "BLE_UpdateService"; //#define UPGRADE_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0" //#define UPGRADE_CHARACTERISTIC1_UUID "abcdef01-2345-6789-1234-56789abcdef1" //#define UPGRADE_CHARACTERISTIC2_UUID "abcdef02-2345-6789-1234-56789abcdef1" NimBLEService *pUpgradeService = nullptr; NimBLECharacteristic *pUpgradeCharacteristic1 = nullptr; NimBLECharacteristic *pUpgradeCharacteristic2 = nullptr; enum WIFI_STAT : byte { WIFI_DISCONNECTED=0, WIFI_BAD_CREDS=1, WIFI_NO_AP=2, WIFI_CONNECTED=3 }; struct updateStatus { WIFI_STAT wifiStatus = WIFI_DISCONNECTED; bool wifiOnline = false; byte wifiIP[4] = {0, 0, 0, 0}; byte currVersion[3] = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH}; byte newVersion[3] = {0, 0, 0}; char wifiSSID[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; }updatePacket; // Class for handling characteristic events class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks { void onWrite(NimBLECharacteristic *pCharacteristic) override { std::string value = pCharacteristic->getValue(); ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str()); if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials // Expecting: "wifi-connect:{\"ssid\":...,\"pass\":...}" size_t jsonStart = (value.size() > 12 && value[12] == ':') ? 13 : 12; if (value.size() <= jsonStart) { ESP_LOGW(tag, "wifi-connect command missing JSON payload"); return; } JsonDocument doc; DeserializationError err = deserializeJson(doc, value.substr(jsonStart)); if (err) { ESP_LOGW(tag, "JSON parse error for wifi-connect: %s", err.c_str()); return; } JsonObject wifiJson = doc.as(); String ssid = wifiJson["ssid"].as(); String pass = wifiJson["pass"].as(); if (ssid.length() == 0) { ESP_LOGW(tag, "wifi-connect missing ssid"); return; } ESP_LOGI(tag, "WiFi connect requested: ssid='%s', pass len=%u", ssid.c_str(), (unsigned)pass.length()); bool started = StartWifiConnectTask(ssid, pass); if (started) { updatePacket.wifiStatus = WIFI_DISCONNECTED; updatePacket.wifiOnline = false; updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0; strncpy(updatePacket.wifiSSID, ssid.c_str(), sizeof(updatePacket.wifiSSID) - 1); } else { ESP_LOGW(tag, "Failed to start WiFi connection task"); } } else if (value.compare("version-check") == 0) { // Check if new version is available ESP_LOGI(tag, "Version check command received: newVersion=%d.%d.%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch()); if(updatePacket.newVersion[0] == 0){ startVersionCheckTask(); // start the task and done }else{ ESP_LOGI(tag, "Version already checked"); } } else if (value.compare("upgrade-start") == 0) { // Start OTA update ESP_LOGI(tag, "Start OTA update command received"); setUpdateModeBoth(); startFirmwareUpdateTask(nullptr); // start the task } else if (value.compare("upgrade-start-files-only") == 0) { // Start OTA update ESP_LOGI(tag, "Start OTA update files-only command received"); setUpdateModeFilesOnly(); startFirmwareUpdateTask(nullptr); // start the task } else if (value.compare("upgrade-start-firmware-only") == 0) { // Start OTA update ESP_LOGI(tag, "Start OTA update firmware-only command received"); setUpdateModeFirmwareOnly(); startFirmwareUpdateTask(nullptr); // start the task } else if (value.compare("rename-device") == 0) { // Start renaming device ESP_LOGI(tag, "Start renane device command received"); } else { ESP_LOGW(tag, "Unknown command received: %s", value.c_str()); } } void onRead(NimBLECharacteristic *pCharacteristic) override { updatePacket.wifiOnline = InternetAvailable; if (WiFi.status() == WL_CONNECTED) { updatePacket.wifiStatus = WIFI_CONNECTED; IPAddress ip = WiFi.localIP(); updatePacket.wifiIP[0] = ip[0]; updatePacket.wifiIP[1] = ip[1]; updatePacket.wifiIP[2] = ip[2]; updatePacket.wifiIP[3] = ip[3]; strncpy(updatePacket.wifiSSID, WiFi.SSID().c_str(), sizeof(updatePacket.wifiSSID) - 1); ESP_LOGI(tag, "WiFi packet: SSID='%s'", updatePacket.wifiSSID); } else { updatePacket.wifiStatus = WIFI_DISCONNECTED; updatePacket.wifiIP[0] = 0; updatePacket.wifiIP[1] = 0; updatePacket.wifiIP[2] = 0; updatePacket.wifiIP[3] = 0; memset(updatePacket.wifiSSID, 0, sizeof(updatePacket.wifiSSID)); } //update version if(otaVersion.major() != 0){ ESP_LOGI(tag, "Updated new version: major=%d, minor=%d, patch=%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch()); updatePacket.newVersion[0] = otaVersion.major(); updatePacket.newVersion[1] = otaVersion.minor(); updatePacket.newVersion[2] = otaVersion.patch(); } // Only populate the control characteristic with the status packet if (pCharacteristic == pUpgradeCharacteristic1) { pCharacteristic->setValue(reinterpret_cast(&updatePacket), sizeof(updatePacket)); ESP_LOGI(tag, "Upgrade status read"); } } }; void bleUpgrade_send_message(String s){ if(pUpgradeCharacteristic2){ if (s.length() == 0) { return; } // Log message details before sending ESP_LOGI(tag, "Sending BLE message, length=%d bytes", s.length()); if (s.length() < 100) { ESP_LOGI(tag, "Message content: '%s'", s.c_str()); } // For testing - ensure string is null-terminated properly String paddedString = s; // Explicitly set using raw bytes with explicit length const char* raw = paddedString.c_str(); size_t rawLen = paddedString.length(); // Explicitly handle null-termination ourselves std::string stdStr(raw, rawLen); pUpgradeCharacteristic2->setValue(stdStr); // Log the value that was actually set std::string valueAfterSet = pUpgradeCharacteristic2->getValue(); ESP_LOGI(tag, "Value after set: length=%d bytes", valueAfterSet.length()); if (pUpgradeCharacteristic2->getSubscribedCount() > 0) { pUpgradeCharacteristic2->notify(); ESP_LOGI(tag, "Notification sent"); } else { ESP_LOGW(tag, "No subscribers for notification"); } } } /* void bleUpgrade_send_message(String s) { if(pUpgradeCharacteristic2) { if (s.length() == 0) { return; } // OPTION 1: Sanitize non-printable characters String sanitized = ""; for (size_t i = 0; i < s.length(); i++) { char c = s[i]; // Only keep printable ASCII characters and common whitespace if ((c >= 32 && c <= 126) || c == '\n' || c == '\r' || c == '\t') { sanitized += c; } else { // Replace non-printable with hexadecimal representation or skip sanitized += String("[0x") + String(c, HEX) + "]"; // OR just: continue; // to skip unprintable chars } } // Set value and notify only if there are subscribers pUpgradeCharacteristic2->setValue(sanitized.c_str()); if (pUpgradeCharacteristic2->getSubscribedCount() > 0) { pUpgradeCharacteristic2->notify(); } } } */ void Init_UpgradeBLEService(NimBLEServer *pServer){ // Create Upgrade BLE Service pUpgradeService= pServer->createService( BTUpgradeServiceUUID.c_str() ); pUpgradeCharacteristic1 = pUpgradeService->createCharacteristic( BTUpgradeCharacteristic1UUID.c_str(), NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY ); // Register the callback with the characteristic pUpgradeCharacteristic1->setCallbacks(new UpgradeChar_Callbacks()); ESP_LOGI(tag, "Upgrade callback registered!"); pUpgradeCharacteristic2 = pUpgradeService->createCharacteristic( BTUpgradeCharacteristic2UUID.c_str(), NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY ); // Register the callback with the characteristic pUpgradeCharacteristic2->setCallbacks(new UpgradeChar_Callbacks()); ESP_LOGI(tag, "Upgrade callback registered!"); pUpgradeService->start(); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->addServiceUUID( BTUpgradeServiceUUID.c_str() ); // Advertise service UUID }