// DEBUG Const
#define DEBUG true //set to true for debug output, false for no debug output
#define DEBUG_SERIAL \
if (DEBUG) Serial
#include <NimBLEDevice.h>
#include <SdFat.h>
#include "ConfigManager.h"
#define SD_SCK 18
#define SD_MISO 19
#define SD_MOSI 23
#define SD_CS 5
#define SENSOR_PIN 35
#define BUTTON_PIN 27
#define RELE_PIN 21
// ================== UUIDs BLE ==================
static const char* SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0";
static const char* CHAR_SENSORS_UUID = "12345678-1234-5678-1234-56789abcdef1";
static const char* CHAR_LOG_UUID = "12345678-1234-5678-1234-56789abcdef3";
SdFat sd;
ConfigManager config(sd);
// ================== Stato globale ==================
enum BleState { BLE_OFF, BLE_ON };
volatile BleState bleState = BLE_OFF;
volatile bool clientSubscribed = false; // true quando il client ha attivato notify
volatile bool logEnabled = false; // true se logging abilitato via comando BLE
// NimBLE handle
NimBLEServer* pServer = nullptr;
NimBLEService* pService = nullptr;
NimBLECharacteristic* pCharSensors = nullptr;
NimBLECharacteristic* pCharLog = nullptr;
// Timer invio
unsigned long lastSampleMs = 0;
bool lastBtn = HIGH;
unsigned long lastDebounceMs = 0;
// ================== Callbacks BLE ==================
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* s) override {
Serial.println("[BLE] Client connesso");
}
void onDisconnect(NimBLEServer* s) override {
Serial.println("[BLE] Client disconnesso");
clientSubscribed = false;
// Advertising continua finché BLE è ON
NimBLEDevice::getAdvertising()->start();
}
};
class SensorsCallbacks : public NimBLECharacteristicCallbacks {
void onSubscribe(NimBLECharacteristic* c, ble_gap_conn_desc* desc, uint16_t subValue) override {
clientSubscribed = (subValue != 0);
Serial.printf("[BLE] Subscribe sensori: %s\n", clientSubscribed ? "ON" : "OFF");
}
void onUnsubscribe(NimBLECharacteristic* c, ble_gap_conn_desc* desc) override {
clientSubscribed = false;
Serial.println("[BLE] Unsubscribe sensori");
}
};
class LogCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic* c) override {
std::string value = c->getValue();
// Parser minimal: cerca "true"/"false"/"1"/"0"
bool newState = false;
if (value.find("true") != std::string::npos || value == "1") {
newState = true;
} else {
newState = false;
}
logEnabled = newState;
Serial.printf("[BLE] Comando log: %s\n", logEnabled ? "ABILITATO" : "DISABILITATO");
}
};
// ================== Funzioni BLE ==================
bool startBLE() {
Serial.println("[BLE] Avvio…");
// Inizializza radio BLE
NimBLEDevice::init("ESP32 Control");
NimBLEDevice::setPower(ESP_PWR_LVL_P9); // opzionale: potenza TX
// NimBLEDevice::setMTU(185); // opzionale: MTU più grande (negoziata con il client)
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
pService = pServer->createService(SERVICE_UUID);
// Characteristic sensori (Notify)
pCharSensors = pService->createCharacteristic(
CHAR_SENSORS_UUID,
NIMBLE_PROPERTY::NOTIFY
);
pCharSensors->setCallbacks(new SensorsCallbacks());
// Characteristic log (Write)
pCharLog = pService->createCharacteristic(
CHAR_LOG_UUID,
NIMBLE_PROPERTY::WRITE
);
pCharLog->setCallbacks(new LogCallbacks());
pService->start();
// Advertising
NimBLEAdvertising* pAdv = NimBLEDevice::getAdvertising();
pAdv->addServiceUUID(SERVICE_UUID);
pAdv->setScanResponse(true);
pAdv->start();
Serial.println("[BLE] Advertising avviato");
digitalWrite(PIN_LED, HIGH);
return true;
}
void stopBLE() {
Serial.println("[BLE] Stop…");
clientSubscribed = false;
logEnabled = false;
// Ferma advertising e chiude server
NimBLEAdvertising* pAdv = NimBLEDevice::getAdvertising();
if (pAdv) pAdv->stop();
// Chiudi servizio e server
if (pService) { pService->stop(); pService = nullptr; }
if (pServer) { pServer->removeService(pService); }
// Deinizializza radio (riduce consumi)
NimBLEDevice::deinit(true);
pServer = nullptr;
pCharSensors = nullptr;
pCharLog = nullptr;
digitalWrite(PIN_LED, LOW);
Serial.println("[BLE] Radio spenta");
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Hello, ESP32!");
// -------------------------------
// GPIO
// -------------------------------
pinMode(SENSOR_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(RELE_PIN, OUTPUT);
analogReadResolution(12); // 0..4095
analogSetAttenuation(ADC_11db); // estende range fino ~3.3V su molti pin
// -------------------------------
// Setup SD
// -------------------------------
uint32_t flash_size = ESP.getFlashChipSize();
DEBUG_SERIAL.print(F("Dimensione Reale Flash: "));
DEBUG_SERIAL.print(flash_size / 1024 / 1024);
DEBUG_SERIAL.println(F(" MB"));
if (!sd.begin(SD_CS, SD_SCK_MHZ(10))) {
sd.initErrorHalt();
}
uint8_t cardType = sd.card()->type();
if (cardType == 0) {
DEBUG_SERIAL.println(F("No SD card attached"));
return;
}
// Serial.print(F("SD Card Type: "));
// // I tipi di carta in SdFat sono diversi, li puoi gestire così:
// if (cardType == SD_CARD_TYPE_SD1) {
// Serial.println(F("SDSC V1"));
// } else if (cardType == SD_CARD_TYPE_SD2) {
// Serial.println(F("SDSC V2"));
// } else if (cardType == SD_CARD_TYPE_SDHC) {
// Serial.println(F("SDHC/SDXC"));
// } else {
// Serial.println(F("UNKNOWN"));
// }
// Ottenere la dimensione
uint64_t cardSize = sd.card()->sectorCount() / (2048);
DEBUG_SERIAL.print(F("SD Card Size: "));
DEBUG_SERIAL.print(cardSize);
DEBUG_SERIAL.println(F("MB"));
if (config.begin()) {
Serial.println("Configurazione pronta.");
} else {
Serial.println("Errore inizializzazione file config!");
}
// Scrittura
if (config.writeValue("setpoint", "25.5")) {
Serial.println("write ok");
}
// Lettura
char result[20];
if (config.readValue("setpoint", result, sizeof(result))) {
Serial.print("Valore trovato: ");
Serial.println(result);
}
}
void loop() {
// --- Gestione tasto (toggle BLE) con debounce ---
bool btn = digitalRead(PIN_BUTTON); // pull-up: HIGH = non premuto, LOW = premuto
if (btn != lastBtn && (millis() - lastDebounceMs) > DEBOUNCE_MS) {
lastDebounceMs = millis();
lastBtn = btn;
if (btn == LOW) { // fronte di pressione
if (bleState == BLE_OFF) {
if (startBLE()) bleState = BLE_ON;
} else {
stopBLE();
bleState = BLE_OFF;
}
}
}
// --- Se BLE attivo, invia periodicamente il valore del sensore ---
if (bleState == BLE_ON && clientSubscribed) {
unsigned long now = millis();
if (now - lastSampleMs >= SAMPLE_PERIOD_MS) {
lastSampleMs = now;
int raw = analogRead(PIN_ADC); // 0..4095
float v = (3.3f * raw) / 4095.0f; // stima tensione (vedi nota calibrazione)
// JSON compatto con timestamp e stato log
char buf[96];
snprintf(buf, sizeof(buf),
"{\"ts\":%lu,\"adc\":%d,\"v\":%.3f,\"log\":%s}",
(unsigned long)now, raw, v, logEnabled ? "true" : "false");
pCharSensors->setValue((uint8_t*)buf, strlen(buf));
pCharSensors->notify();
if (logEnabled) {
Serial.printf("[LOG] ts=%lu adc=%d v=%.3f\n", (unsigned long)now, raw, v);
}
}
}
// piccolo respiro
delay(1);
}