/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini

    Δημιουργία ενός BLE Server ο οποίος, όταν δεχτεί σύνδεση, θα στέλνει περιοδικές ειδοποιήσεις (notifications).
    Η υπηρεσία (service) διαφημίζεται με UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
    Έχει ένα χαρακτηριστικό (characteristic): 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - για λήψη δεδομένων ("WRITE") 
    Έχει ένα χαρακτηριστικό: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - για αποστολή δεδομένων ("NOTIFY")

    Βήματα δημιουργίας του BLE Server:
    1. Δημιουργία BLE Server
    2. Δημιουργία BLE Service
    3. Δημιουργία BLE Characteristic στην Υπηρεσία (Service)
    4. Δημιουργία BLE Descriptor στο χαρακτηριστικό
    5. Εκκίνηση της Υπηρεσίας (Service)
    6. Εκκίνηση της διαφήμισης (Advertising)
*/

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

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;

bool deviceConnected = false;
bool oldDeviceConnected = false;

uint8_t txValue = 0;
int state = 0;

// Ορισμός των UUIDs για την Υπηρεσία και τα Χαρακτηριστικά
#define SERVICE_UUID           "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID_TX "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"

// Ορισμός των Pins για το LED και το Κουμπί
#define ledPin 23
#define buttonPin 22 

// Κλάση διαχείρισης συμβάντων (callbacks) του Server (Σύνδεση/Αποσύνδεση)
class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
        deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
        deviceConnected = false;
    }
};

// Κλάση διαχείρισης συμβάντων (callbacks) των Χαρακτηριστικών (Λήψη Δεδομένων)
class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
        String rxValue = pCharacteristic->getValue();

        if (rxValue.length() > 0) {
            Serial.print("RX --> ");
            for (int i = 0; i < rxValue.length(); i++) {
                Serial.print(rxValue[i]);
            }
            Serial.println();
            
            // Έλεγχος του πρώτου χαρακτήρα για την ενεργοποίηση/απενεργοποίηση του LED
            if (rxValue[0] != '0') {
                digitalWrite(ledPin, HIGH);
            } else {
                digitalWrite(ledPin, LOW);
            }
        }
    }
};


void setup() {
    Serial.begin(115200);
    
    // Αρχικοποίηση των Pins
    pinMode(ledPin, OUTPUT);
    pinMode(buttonPin, INPUT);
    
    // Δημιουργία της BLE Συσκευής
    BLEDevice::init("UART Service");

    // Δημιουργία του BLE Server
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    // Δημιουργία της Υπηρεσίας BLE (Service)
    BLEService *pService = pServer->createService(SERVICE_UUID);

    // Δημιουργία του Χαρακτηριστικού Εκπομπής (TX)
    pTxCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_TX,
        BLECharacteristic::PROPERTY_NOTIFY
    );
                      
    pTxCharacteristic->addDescriptor(new BLE2902());

    // Δημιουργία του Χαρακτηριστικού Λήψης (RX)
    BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_RX,
        BLECharacteristic::PROPERTY_WRITE
    );

    pRxCharacteristic->setCallbacks(new MyCallbacks());

    // Εκκίνηση της Υπηρεσίας (Service)
    pService->start();
    
    // Ρύθμιση και Εκκίνηση της Διαφήμισης (Advertising)
    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
    pAdvertising->addServiceUUID(SERVICE_UUID);
    pAdvertising->setScanResponse(true);
    pAdvertising->setMinPreferred(0x06); // βοηθά σε προβλήματα σύνδεσης με iPhone
    pAdvertising->setMinPreferred(0x12);
    
    BLEDevice::startAdvertising();
    Serial.println("To characteristic γίνεται advertized...");

    pServer->getAdvertising()->start();
    Serial.println("Αναμονή για σύνδεση πελάτη (client) ώστε να σταλεί ειδοποίηση...");
}

void loop() {
    // Αν υπάρχει συνδεδεμένη συσκευή (client)
    if (deviceConnected) {
        // Ανάγνωση της κατάστασης του κουμπιού
        state = digitalRead(buttonPin);

        // Ορισμός της τιμής εκπομπής (TX) με βάση το κουμπί
        String newValue = String(state);
        
        // Αποστολή (Notify) της νέας τιμής
        pTxCharacteristic->setValue(newValue.c_str());
        pTxCharacteristic->notify();
        
        Serial.print("TX --> ");
        Serial.println(newValue);
        
        // Καθυστέρηση για αποφυγή συμφόρησης της στοίβας bluetooth
        delay(1000); 
    }
    
    // Χειρισμός αποσύνδεσης: Αν η συσκευή μόλις αποσυνδέθηκε
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // Χρόνος στη στοίβα bluetooth για προετοιμασία
        pServer->startAdvertising(); // Επανεκκίνηση της διαφήμισης (advertising)
        Serial.println("Επανεκκίνηση διαφήμισης (advertising)");
        oldDeviceConnected = deviceConnected;
    }
    
    // Χειρισμός νέας σύνδεσης: Αν μια συσκευή μόλις συνδέθηκε
    if (deviceConnected && !oldDeviceConnected) {
        // Εδώ μπορούν να γίνουν ενέργειες μόλις γίνει η σύνδεση
        oldDeviceConnected = deviceConnected;
    }
}
