diff --git a/soilReporterNode/.theia/launch.json b/soilReporterNode/.theia/launch.json new file mode 100644 index 0000000..a2ea02c --- /dev/null +++ b/soilReporterNode/.theia/launch.json @@ -0,0 +1,6 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + "version": "0.2.0", + "configurations": [] +} diff --git a/soilReporterNode/soilReporterNode.ino b/soilReporterNode/soilReporterNode.ino new file mode 100644 index 0000000..90c5a55 --- /dev/null +++ b/soilReporterNode/soilReporterNode.ino @@ -0,0 +1,278 @@ +#include // ESP32 WiFiManager library for captive portal configuration +#include +#include +#include +#include +#include +#include +using std::vector; + +// --- Debug Logging: Uncomment to enable debugging --- +#define DEBUG 1 +#ifdef DEBUG + #define DEBUG_PRINT(x) Serial.print(x) + #define DEBUG_PRINTLN(x) Serial.println(x) +#else + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) +#endif + +// Global configuration parameters +String apiServer = "http://192.168.1.193:8000/api/soilmoisture"; // Default API server URL +int scanningDelaySec = 30; // Default scanning delay in seconds + +// WiFiManager parameters for additional configuration +WiFiManagerParameter customServer("server", "API Server URL", apiServer.c_str(), 100); +WiFiManagerParameter customScanDelay("scandelay", "Scan Delay (sec)", String(scanningDelaySec).c_str(), 10); + +// Create a WiFiManager instance +WiFiManager wifiManager; + +// Global variable for the API key (modify as needed) +//const char* apiKey = "YOUR_UNIQUE_API_KEY_HERE"; + +// BLE configuration +#define SCAN_TIME 5 // Scan duration in seconds for each scan cycle +static BLEUUID serviceUUID("12345678-1234-1234-1234-123456789abc"); +static BLEUUID charUUID("abcd1234-5678-1234-5678-abcdef123456"); + +// Global vector to store discovered sensor BLE addresses +vector foundSensors; + +// State machine for controlling the reporter node +enum ReporterState { + STATE_SCANNING, + STATE_CONNECTING, + STATE_WAITING +}; +ReporterState currentState = STATE_SCANNING; +unsigned long waitStartTime = 0; + +// Forward declarations of functions +bool connectToSensor(const BLEAddress &addr); +bool sendDataToAPI(const String &sensorAddress, float moistureValue); + +// --- BLE Advertised Device Callback: Collect sensor addresses --- +class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { + public: + void onResult(BLEAdvertisedDevice advertisedDevice) override { + if (advertisedDevice.haveServiceUUID() && + advertisedDevice.isAdvertisingService(serviceUUID)) { + BLEAddress addr = advertisedDevice.getAddress(); + // Avoid duplicates + bool alreadyAdded = false; + for (auto &existing : foundSensors) { + if (existing.equals(addr)) { + alreadyAdded = true; + break; + } + } + if (!alreadyAdded) { + DEBUG_PRINT("Found sensor: "); + DEBUG_PRINTLN(addr.toString().c_str()); + foundSensors.push_back(addr); + } + } + } +}; + +// --- Minimal BLE Client Callbacks --- +class MyClientCallbacks : public BLEClientCallbacks { + public: + void onConnect(BLEClient* pClient) override { + DEBUG_PRINTLN("BLE Client connected."); + } + void onDisconnect(BLEClient* pClient) override { + DEBUG_PRINTLN("BLE Client disconnected."); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println(F("Starting Reporter Node with Captive Portal Configuration...")); + + // --- WiFiManager Captive Portal Configuration --- + // Add custom parameters so the user can configure the API server URL and scan delay. + wifiManager.addParameter(&customServer); + wifiManager.addParameter(&customScanDelay); + + // Optionally force configuration mode (for example, via a hardware button or by calling resetSettings()) + // Uncomment the next line to force configuration mode for testing: + //wifiManager.resetSettings(); + + // Attempt autoConnect; if there’s no saved config, the captive portal AP "ConfigAP" (with password "configpassword") is created. + if (!wifiManager.autoConnect("PlantPalConfig", "plantpal")) { + DEBUG_PRINTLN("Failed to connect and hit timeout. Restarting..."); + delay(4000); + ESP.restart(); + } + Serial.println(F("WiFi connected via captive portal.")); + + // Retrieve updated configuration parameters from WiFiManager: + apiServer = String(customServer.getValue()); + scanningDelaySec = (customScanDelay.getValue() != nullptr) ? atoi(customScanDelay.getValue()) : scanningDelaySec; + DEBUG_PRINT("Configured API Server: "); + DEBUG_PRINTLN(apiServer); + DEBUG_PRINT("Configured Scan Delay (sec): "); + DEBUG_PRINTLN(scanningDelaySec); + + // --- Initialize BLE --- + BLEDevice::init(""); + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->setInterval(1349); + pBLEScan->setWindow(449); + pBLEScan->start(SCAN_TIME, false); + + currentState = STATE_SCANNING; +} + +void loop() { + // Optionally, if you're using a hardware watchdog, call esp_task_wdt_reset() here. + // esp_task_wdt_reset(); + + switch(currentState) { + case STATE_SCANNING: { + // Start a new scan cycle + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->start(SCAN_TIME, false); + pBLEScan->clearResults(); + if (!foundSensors.empty()) { + currentState = STATE_CONNECTING; + DEBUG_PRINTLN("Transitioning to STATE_CONNECTING"); + } + break; + } + + case STATE_CONNECTING: { + if (!foundSensors.empty()) { + BLEAddress addr = foundSensors.front(); + foundSensors.erase(foundSensors.begin()); + DEBUG_PRINT("Processing sensor: "); + DEBUG_PRINTLN(addr.toString().c_str()); + if (connectToSensor(addr)) { + DEBUG_PRINTLN("Sensor processed successfully."); + } else { + DEBUG_PRINTLN("Failed to process sensor."); + } + } + // If no more sensors remain, switch to waiting state. + if (foundSensors.empty()) { + currentState = STATE_WAITING; + waitStartTime = millis(); + DEBUG_PRINTLN("All sensors processed. Transitioning to STATE_WAITING."); + } + break; + } + + case STATE_WAITING: { + if (millis() - waitStartTime >= (unsigned long)scanningDelaySec * 1000) { + currentState = STATE_SCANNING; + DEBUG_PRINTLN("Transitioning to STATE_SCANNING"); + foundSensors.clear(); + } + break; + } + + default: + break; + } + + delay(10); // Small delay to yield time to other tasks +} + +bool connectToSensor(const BLEAddress &addr) { + BLEAddress sensorAddr = addr; // Make a local copy for printing + DEBUG_PRINT("Connecting to sensor: "); + DEBUG_PRINTLN(sensorAddr.toString().c_str()); + + BLEClient* pClient = BLEDevice::createClient(); + pClient->setClientCallbacks(new MyClientCallbacks()); + + if (!pClient->connect(sensorAddr)) { + DEBUG_PRINTLN("Failed to connect."); + pClient->disconnect(); + return false; + } + DEBUG_PRINTLN("Connected to sensor."); + + pClient->setMTU(517); // Optionally set a larger MTU + + BLERemoteService* pService = pClient->getService(serviceUUID); + if (pService == nullptr) { + DEBUG_PRINT("Failed to find service UUID: "); + DEBUG_PRINTLN(serviceUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + DEBUG_PRINTLN("Service found."); + + BLERemoteCharacteristic* pCharacteristic = pService->getCharacteristic(charUUID); + if (pCharacteristic == nullptr) { + DEBUG_PRINT("Failed to find characteristic UUID: "); + DEBUG_PRINTLN(charUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + DEBUG_PRINTLN("Characteristic found."); + + if (pCharacteristic->canRead()) { + String value = pCharacteristic->readValue(); + DEBUG_PRINT("Sensor Value: "); + DEBUG_PRINTLN(value); + + float moistureValue = value.toFloat(); + if (sendDataToAPI(sensorAddr.toString(), moistureValue)) { + DEBUG_PRINTLN("Data sent to API successfully."); + } else { + DEBUG_PRINTLN("Failed to send data to API."); + } + } else { + DEBUG_PRINTLN("Characteristic not readable."); + } + + pClient->disconnect(); + return true; +} + +bool sendDataToAPI(const String &sensorAddress, float moistureValue) { + if (WiFi.status() != WL_CONNECTED) { + DEBUG_PRINTLN("Wi-Fi not connected."); + return false; + } + + HTTPClient http; + http.begin(apiServer); + http.addHeader("Content-Type", "application/json"); + //http.addHeader("X-API-KEY", apiKey); + + // Construct the JSON payload based on the model: + // { + // "deviceId": "sensorAddress", + // "moistureLevel": moistureValue, + // "device": { + // "deviceId": "sensorAddress", + // "nickname": "SoilSensor", + // "soilMoistures": [] + // } + // } + String payload = String("{\"deviceId\":\"") + + sensorAddress + + String("\",\"moistureLevel\":") + + String(moistureValue) + + String("}"); + + DEBUG_PRINT("Sending payload: "); + DEBUG_PRINTLN(payload); + + int httpResponseCode = http.POST(payload); + DEBUG_PRINT("HTTP Response code: "); + DEBUG_PRINTLN(httpResponseCode); + String response = http.getString(); + DEBUG_PRINT("Response: "); + DEBUG_PRINTLN(response); + + http.end(); + return (httpResponseCode > 0); +}