/********* Pleasedontcode.com **********

	Pleasedontcode thanks you for automatic code generation! Enjoy your code!

	- Terms and Conditions:
	You have a non-exclusive, revocable, worldwide, royalty-free license
	for personal and commercial use. Attribution is optional; modifications
	are allowed, but you're responsible for code maintenance. We're not
	liable for any loss or damage. For full terms,
	please visit pleasedontcode.com/termsandconditions.

	- Project: "BLE Client"
	- Source Code NOT compiled for: ESP32 DevKit V1
	- Source Code created on: 2025-02-11 18:03:14

********* Pleasedontcode.com **********/

/****** SYSTEM REQUIREMENTS *****/
/****** SYSTEM REQUIREMENT 1 *****/
	/* **ESP32 BLE Client (Stable & Crash-Free)**    - */
	/* Scans for `PERIPH`, connects when found, auto- */
	/* reconnects if lost.    - Separate tasks for BLE */
	/* connection & Modbus RTU handling.    - FreeRTOS, */
	/* mutexes, watchdogs, and memory tracking to prevent */
	/* crased */
/****** END SYSTEM REQUIREMENTS *****/

/* START CODE */

/****** DEFINITION OF LIBRARIES *****/
#include <ArduinoBLE.h>	//https://github.com/arduino-libraries/ArduinoBLE
#include <DHT.h>	//https://github.com/adafruit/DHT-sensor-library
#include <BLEDevice.h> // Added for BLE functionality

/****** FUNCTION PROTOTYPES *****/
void setup(void);
void loop(void);

/***** DEFINITION OF DIGITAL INPUT PINS *****/
const uint8_t myDHT22_DHT22_DOUT_PIN_D4		= 4;

// Define UUIDs for BLE service and characteristics
static BLEUUID serviceUUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
static BLEUUID charUUID("6e400003-b5a3-f393-e0a9-E50E24DCCA9E");
static BLEUUID rxUUID("6e400002-b5a3-f393-e0A9-E50E24DCCA9E");

// Global variables
static bool connected = false;
static BLEClient* pClient = nullptr;
static BLERemoteService* pRemoteService = nullptr;
static BLERemoteCharacteristic* pRemoteCharacteristic = nullptr;
static BLERemoteCharacteristic* pRemoteRxCharacteristic = nullptr;
static BLEAdvertisedDevice* myDevice = nullptr;
static BLEScan* pBLEScan = nullptr;

// BLE Client callback
class MyClientCallback : public BLEClientCallbacks {
    void onConnect(BLEClient* pclient) {
        Serial.println("Connected to peripheral.");
    }
    void onDisconnect(BLEClient* pclient) {
        connected = false;
        Serial.println("Disconnected from peripheral.");
    }
};

// Task: Scan for BLE devices and connect
void scanAndConnectTask(void* pvParameters) {
    esp_task_wdt_add(NULL);  // Enable watchdog for this task
    while (1) {
        Serial.println("Scanning for BLE devices...");
        pBLEScan->start(5, false);
        vTaskDelay(1000 / portTICK_PERIOD_MS);

        BLEScanResults foundDevices = *pBLEScan->getResults();  // FIXED POINTER ERROR
        for (int i = 0; i < foundDevices.getCount(); i++) {
            BLEAdvertisedDevice advertisedDevice = foundDevices.getDevice(i);
            Serial.println("Found Device: " + advertisedDevice.toString());

            if (advertisedDevice.haveName() && advertisedDevice.getName() == "PERIPH") {
                pBLEScan->stop();
                myDevice = new BLEAdvertisedDevice(advertisedDevice);
                Serial.println("Target device found. Connecting...");

                if (connectToServer()) {
                    Serial.println("Connected successfully.");
                } else {
                    Serial.println("Connection failed. Retrying...");
                }
                myDevice = nullptr;
            }
        }
        printMemoryUsage("scanAndConnectTask");
        vTaskDelay(5000 / portTICK_PERIOD_MS);
        esp_task_wdt_reset();  // Reset watchdog to prevent task hang
    }
}

// Function: Connect to BLE server
bool connectToServer() {
    if (pClient != nullptr && pClient->isConnected()) {
        return true;
    }

    pClient = BLEDevice::createClient();
    pClient->setClientCallbacks(new MyClientCallback());

    if (!pClient->connect(myDevice)) {
        Serial.println("Failed to connect to peripheral.");
        delete pClient;
        pClient = nullptr;
        return false;
    }

    pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
        Serial.println("Failed to find service UUID.");
        pClient->disconnect();
        return false;
    }

    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    pRemoteRxCharacteristic = pRemoteService->getCharacteristic(rxUUID);
    if (pRemoteCharacteristic == nullptr || pRemoteRxCharacteristic == nullptr) {
        Serial.println("Failed to find characteristics.");
        pClient->disconnect();
        return false;
    }

    connected = true;
    return true;
}

// Task: Send and receive data
void sendReceiveTask(void* pvParameters) {
    esp_task_wdt_add(NULL);  // Enable watchdog for this task
    while (1) {
        if (connected) {
            sendReadRequestToSlave(0x43, 5);
            readDataFromPeripheral();
        }
        printMemoryUsage("sendReceiveTask");
        vTaskDelay(5000 / portTICK_PERIOD_MS);
        esp_task_wdt_reset();  // Reset watchdog to prevent task hang
    }
}

// Function: Send read request to slave device
void sendReadRequestToSlave(uint16_t startingAddress, uint16_t length) {
    Serial.println("Sending read request...");
    if (!connected || pRemoteRxCharacteristic == nullptr) {
        Serial.println("Not connected or RX characteristic unavailable.");
        return;
    }

    uint8_t requestData[8] = {
        0x20, 0x03, 
        (startingAddress >> 8) & 0xFF, startingAddress & 0xFF,
        (length >> 8) & 0xFF, length & 0xFF, 
        0x72, 0xAC
    };

    pRemoteRxCharacteristic->writeValue(requestData, sizeof(requestData));
}

// Function: Read data from peripheral
void readDataFromPeripheral() {
    if (!connected || pRemoteCharacteristic == nullptr) {
        Serial.println("Not connected or TX characteristic unavailable.");
        return;
    }

    String value = pRemoteCharacteristic->readValue();
    Serial.print("Received Data: ");
    Serial.println(value);

    Serial.print("Data (HEX): ");
    for (size_t i = 0; i < value.length(); i++) {
        Serial.print((uint8_t)value[i], HEX);
        Serial.print(" ");
    }
    Serial.println();
}

// Function: Print memory usage
void printMemoryUsage(const char* taskName) {
    Serial.print("[MEMORY] Task: ");
    Serial.print(taskName);
    Serial.print(" | Free Heap: ");
    Serial.print(esp_get_free_heap_size());
    Serial.print(" bytes | Min Heap: ");
    Serial.print(esp_get_minimum_free_heap_size());
    Serial.println(" bytes");
}

void setup(void)
{
    Serial.begin(57600); // Initialize Serial for debugging
    Serial.println("Starting BLE Client...");
    BLEDevice::init("CENTRAL");

    // Set TX power
    esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9); // This line may not be compatible with ArduinoBLE library

    // Initialize BLE scan
    pBLEScan = BLEDevice::getScan();
    pBLEScan->setInterval(2000);
    pBLEScan->setWindow(1000);
    pBLEScan->setActiveScan(true);

    // Create tasks on specific cores
    xTaskCreatePinnedToCore(scanAndConnectTask, "Scan Task", 10240, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(sendReceiveTask, "Data Task", 10240, NULL, 2, NULL, 1);

    pinMode(myDHT22_DHT22_DOUT_PIN_D4, INPUT_PULLUP); // Initialize DHT sensor pin
}

void loop(void)
{
    // put your main code here, to run repeatedly:
    delay(500); // Delay to prevent overwhelming the loop
}

/* END CODE */