From 42aa91f4791128369799d1b57d4c66f1211f6d5e Mon Sep 17 00:00:00 2001 From: programmingPug <36635276+programmingPug@users.noreply.github.com> Date: Sat, 4 Jan 2025 14:38:42 -0500 Subject: [PATCH] Updates to API and added arduino code for the XIAO-ESP32C6 --- .../DesignTimeBuild/.dtbcache.v2 | Bin 166030 -> 166030 bytes house-plant-api/.vs/house-plant-api/v17/.suo | Bin 71680 -> 71680 bytes .../Services/DevicePollingService.cs | 2 +- house-plant-api/devices.db | Bin 12288 -> 4096 bytes house-plant-api/devices.db-shm | Bin 32768 -> 32768 bytes house-plant-api/devices.db-wal | Bin 0 -> 222512 bytes soilProbeESP32C6/.theia/launch.json | 8 + soilProbeESP32C6/soilProbeESP32C6.ino | 405 ++++++++++++++++++ 8 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 soilProbeESP32C6/.theia/launch.json create mode 100644 soilProbeESP32C6/soilProbeESP32C6.ino diff --git a/house-plant-api/.vs/house-plant-api/DesignTimeBuild/.dtbcache.v2 b/house-plant-api/.vs/house-plant-api/DesignTimeBuild/.dtbcache.v2 index 806b254d0a733c593bd252d628934a437c0d912b..ca982bc061350f815bc5e63ca36b08f6abefffd6 100644 GIT binary patch delta 24 gcmeC1$kjKIYr+a8Mm%xdeja8Xu1hg diff --git a/house-plant-api/.vs/house-plant-api/v17/.suo b/house-plant-api/.vs/house-plant-api/v17/.suo index f9683ca843b2ebe02f4d6632d4ea0c95dab1c0b8..f6585ae2d50ce07b8e6c73fa26a2bb8ee32a111e 100644 GIT binary patch delta 2416 zcmd5-Z%kWN6z{!#kJ9=IltQV1qd-OIs428VWegeZfGmW8(PUXN-1wuLE@mvx=@QNA zvN&D%0PiF=#tf5ZviQYVdQpt=gA;UWOx&g+QQWe9a7)aVm{~Wcj(Sdcg=QaM7(aND z-@Esmd(XZ1{mwb}M&nX6E{(Mm+e{{t8L^N`rLZwE#J!V!e=+8RN?wsO_Ex)Rt{)EY za|%t95jZmi4u=<1^EQ~_4fbcz*ki{JiUpWda<~e6MF^To15Tq)dLH&4MAT>5$S$E? ziRed^8)I~;+Xz4EO^8y&tHv1lxUla*(7Y`1rOJ#+R&0HUVneURHXo6L2q1RAsTyZj z4fY;J&=QYe`#54F;y%OHX0$ZE8T;*sFd~B3g6KeWBA{FjSm`WJpi&2pvSQqeoHFwC zcwaSqDZ9t_*Rz+$`^#~#sjTaD^_6~l9r;w7IY(SNN7*uyZ~_zf@})5 z#C0ZQ8O}2t-@#dmIM1v{{;P1_d?)Ekoaf>~#JK}O3;ho{XHjv>YLPToS8lU$O3G-V zx<*;T7iM-w$>Gi#0(E^l*_B~59zc+2d0nv{iPZ}=?|YW%;R-dJ1CMN`;t*Dgvy6g!@6RVzoe!U)Xr07Uml@M2-Nv>wOPdqJ{rn{1*2 zUQ|_QFU;n*Xw9m;35}iNQ1e#&6H4w7BD0f1fk!LMnANHYJ?(A z@(SVz;wXZ)%W!)P_2Y((?1htk@3Zm!wYYWN-TJr7;oh3f-!7`z`%O+W@AA zw_90<#_H^Wq!$yb4lPvpOVic+@vo~ItZisa=MdZub}EIaJb+l97D{J5Btzd)v}Jk& z6miRG;fY-eE3w?LY{lEXbR@Z@eJ*se%VfH`v!8W{>fPtLzK84kB#5h}qBL>EEZC9M ze|d7H4UdvOIX3!H3C_OY7yg6aneRS9T9y+8%LOT-C(9cg3=TI0LmT{|Ktq#1G}!o* ze_${;;BOiT1a~(M4>b%81#PC&z}|cvdM;dm*ynq-P-HFC!N~kgaq|nSHSxv?CQkg2 zCpqYST9n#PDF{~a$@$aVL3emY=nXZA(FxTFPfq%T@1tS{ajgU5uUTQ@+B5o}(n=u# z3<(wcKsqiTE{8_F!eYPK*$8~Cx}o>72jXXQM0|TMOH46NL6ZajmKuT7(>3C3U} zeEfR{@}ytqu{kH}7TpUUm}5+0U&S<;JqHD<4Q%~B_F3$biyewZWY#ksvaxT>^bhT2 z2V=(zSmX4&d2BnwwNDl>4sGQ!_)@OvG8g-q!MVNlqW(HQ1;*-A4BvR+eCZnd2SMvQ A8~^|S delta 2398 zcmb7FeQZ-z6z{oxZC7Z=R@esw zQ81*6?LnF-67yn~1&MaM>NFBF7zxH08A$kpXvCO|Kh(sKMKc+#=X8Cufc~+Q{CYm# zxwr4!-#Pd7#+BZ<^47Y%LY+>hCnS=|Bq<#bss{r107`MOSY*8YvHaGap+!!^tye{M#MpFO72_2?d_poy@Y@O(ysD{VVTw-CAs zZbA*=J}q8FdJTa$Iz~E=u$Hhe9cxHye!h|HI>JtZm#~S@NJu0F9oYhd)3J@b?S!WZ z&k$M(ypm-l*<6AqOCy0&a6|$Xc*al&uS5a`$`MMzN${u18NOdLGjed;G_PkKZNinY z@c%J2Z0C~J=wY6@T7I5M4^uIlpwY94Y+hTO)=Urk9?}{;Ob1br+KcGvNhOp~U!$D$i7A4*Nrs^sKjyqsKrK} z)`L@%mt7^xciw_%wl9mPS6eSb*F)(5n@W=vbIN2xg z-dT%*zjj;Yg~*kZo6X+}{_e1N2ee(~!nc35;l_O>vZU)Ly@bz_fD6ORWmSJ3;KgGV z*!A9N^^&5V6L_@SBRwH%p!34nIJ2^*i;(fJ{U-HwMJ*Gtu^c$@-+kg_=3b@uWXT&2$dh#|ouy>~p|@{$g1ie#4N<)lW&zFi(GT>LiNzI-e5T@c*93FN%6s z;KfsIc=g>Jx%Zt@W_3~we$Z&Z#%o`w=aj(&DAP@STEI7ODfV*?9FC3Yp$X35{tx%z z51ke~+vS!1yK%tYJ(aR@^2UR$u8;NVp#v)?ZLt&CP&L#)3wA(XHV8E#aCxF6RwAJP K(VhEpK>P#9rEx0& diff --git a/house-plant-api/Services/DevicePollingService.cs b/house-plant-api/Services/DevicePollingService.cs index 47d4e65..ff5817c 100644 --- a/house-plant-api/Services/DevicePollingService.cs +++ b/house-plant-api/Services/DevicePollingService.cs @@ -16,7 +16,7 @@ private readonly ILogger _logger; private readonly BluetoothService _bluetoothService; private readonly IDbContextFactory _dbContextFactory; - private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(30); + private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(10); public DevicePollingService( ILogger logger, diff --git a/house-plant-api/devices.db b/house-plant-api/devices.db index 2945bcd7412080b3153cb887ba9c2f36113c9b30..0de02ecf623141161c863ee065d9f7dd83cbe849 100644 GIT binary patch delta 32 mcmZojXi%6S&C1BYz`!`M(4L!-L9eQi7sz7}*es|pjUNDR=mtLk literal 12288 zcmeI&U2D@&7zgl^R2*z{)vF=BIRu6)TT9+dU#^zMOv2K%CMnpf5xWSXU1FO=(M$an zeigrxcYXn%j;3rw@y09pKjiJ?>-5p5q+D(d>zsuG)QNcC#goz?M=bSMn(pjUk zDuEszRRg+j#(yi3bw01^^p$w{jUE1=3km`dfB*y_009U<00Izz00ba#PhcWGJ~}-W zW<~UN8Rx4dZBwuEcvZ&f{Nb=>`L-F_JT!-{%^!Swl14Z28FjY4JQeninoZ&Br`ybz z@rQ(>wm0!!hO~Vv@ss*Kr;?m&i923szp{PqjYIBDU6)&9F9>~;-g9Ykd9`br_|C}m zXZ+HhadR4u9gosSwiljl(>z*}#6$ZH1#QE2Q^|a>S@IvIr)5Gb`EBPYOY)*zQ|E4> zw8$#gjq)OhKUr6>TN}BYcf}if!-hJYDbq1 zL)H~bpr~iZ0!8U}qO53MlzGn@Uf8BJ>?^Vw-VIR)YImS0ou1a~8vX8O|1UItGCEKY hfB*y_009U<00Izz00bZa0SNp*0tZ5^DO7(w`~rFKO#=&4EM zKZGIESkO6{=S{xzot&KfmuCTA>!On?d!=eg^Khk>S(P8U>|Cgn1(=E@xeYyL5-^$*pm#X&e@74PtRqy=0JCx%K0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7e?KLx6(6GC7(1?qW}M#l0u6qAwYlt z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ LfB*pk|C_)KD%UQ> delta 84 zcmZo@U}|V!;+1%$%K!t6lM@-GMJ?DR*i0wi=ERT@fyuzs{|ADJIWn6I82cQ7#4B3> DxjYmQ diff --git a/house-plant-api/devices.db-wal b/house-plant-api/devices.db-wal index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9e352c444cffa5866c9964ae1ee3c14747852915 100644 GIT binary patch literal 222512 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 literal 0 HcmV?d00001 diff --git a/soilProbeESP32C6/.theia/launch.json b/soilProbeESP32C6/.theia/launch.json new file mode 100644 index 0000000..7e4253b --- /dev/null +++ b/soilProbeESP32C6/.theia/launch.json @@ -0,0 +1,8 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + "version": "0.2.0", + "configurations": [ + + ] +} diff --git a/soilProbeESP32C6/soilProbeESP32C6.ino b/soilProbeESP32C6/soilProbeESP32C6.ino new file mode 100644 index 0000000..911a1e8 --- /dev/null +++ b/soilProbeESP32C6/soilProbeESP32C6.ino @@ -0,0 +1,405 @@ +#include +#include +#include +#include +#include +#include +#include + +// ======== BLE UUIDs ======== +#define SERVICE_UUID "12345678-1234-1234-1234-123456789abc" +#define CHARACTERISTIC_UUID "abcd1234-5678-1234-5678-abcdef123456" + +// ======== Hardware & ADC ======== +#define MOISTURE_PIN A0 +#define LED_PIN 2 // Example LED pin (built-in LED on many ESP32 boards) +#define BUTTON_PIN D4 // GPIO 4 for calibration button (recommended over GPIO 0) + +// ======== Constants ======== +const int DRY_VALUE_DEFAULT = 500; +const int WET_VALUE_DEFAULT = 200; +const int ADC_RESOLUTION = 10; // 10-bit ADC (0–1023) + +// ======== Reading & Averaging ======== +const int NUM_SAMPLES = 5; // Number of samples to average +const int SAMPLE_DELAY_MS = 50; // Delay between each sample (ms) + +// ======== Connection Timeout Settings ======== +const unsigned long CONNECTION_TIMEOUT_MS = 120000; // 2 minutes to establish connection +unsigned long connectionStartTime = 0; +bool connectionTimerStarted = false; + +// ======== Sleep Settings ======== +const uint64_t DEEP_SLEEP_DURATION_US = 60ULL * 60ULL * 1000000ULL; // 1 hour in microseconds + +// ======== Calibration Variables ======== +int dryValue = DRY_VALUE_DEFAULT; // Initialize with default dry value +int wetValue = WET_VALUE_DEFAULT; // Initialize with default wet value + +// ======== Device Configuration ======== +const char* deviceName = "003-SoilSensor"; // Default device name + +// ======== EEPROM Addresses ======== +const int EEPROM_SIZE = 512; +const int EEPROM_DRY_ADDRESS = 0; // Bytes 0-3 +const int EEPROM_WET_ADDRESS = 4; // Bytes 4-7 + +// ======== BLE Objects ======== +BLEServer* pServer = nullptr; +BLECharacteristic* pCharacteristic = nullptr; + +// ======== Device State Flags ======== +bool isConnected = false; +bool dataSent = false; + +// ======== Calibration Variables ======== +enum DeviceState { + STATE_NORMAL, + STATE_CALIBRATE_NOT_IN_SOIL, + STATE_CALIBRATE_IN_SOIL +}; + +DeviceState currentState = STATE_NORMAL; +int calibrationCount = 0; + +// ======== Button Handling ======== +const unsigned long DEBOUNCE_DELAY = 50; // 50ms debounce delay +bool lastButtonState = HIGH; // Assuming pull-up resistor +bool buttonPressed = false; +unsigned long lastDebounceTime = 0; + +// ======== LED Blinking Parameters ======== +unsigned long previousBlinkTime = 0; +const unsigned long BLINK_ON_DURATION = 200; // 200ms ON +const unsigned long BLINK_OFF_DURATION = 100; // 100ms OFF +bool ledState = false; + +// ======== Function Prototypes ======== +int readSoilMoisture(); +int getAveragedMoisture(int numSamples); +void enterDeepSleep(); +void handleCalibrationButtonPress(); +void readCalibrationValues(); +void writeCalibrationValues(int dry, int wet); +void checkButtonPress(); +void updateLED(); + +// ======== Custom Server Callbacks ======== +class MyServerCallbacks : public BLEServerCallbacks { + void onConnect(BLEServer* pServer) override { + Serial.println("Client connected."); + isConnected = true; + + // Turn on LED to indicate active state + digitalWrite(LED_PIN, HIGH); + + // Stop connection timeout timer + connectionTimerStarted = false; + connectionStartTime = 0; + } + + void onDisconnect(BLEServer* pServer) override { + Serial.println("Client disconnected."); + isConnected = false; + dataSent = false; + + // Restart advertising to allow new connections + pServer->startAdvertising(); + + // Restart connection timeout timer + connectionTimerStarted = true; + connectionStartTime = millis(); + + // Turn off LED to indicate sleep state + digitalWrite(LED_PIN, LOW); + } +}; + +// ======== Sensor Reading Functions ======== +/** + * Perform a single soil moisture reading (0–100%). + */ +int readSoilMoisture() { + int rawValue = analogRead(MOISTURE_PIN); + int percent = map(rawValue, dryValue, wetValue, 0, 100); + return constrain(percent, 0, 100); +} + +/** + * Gathers multiple samples, applies a small delay between each, + * averages them, and returns the final moisture value. + */ +int getAveragedMoisture(int numSamples) { + long sum = 0; + + for (int i = 0; i < numSamples; i++) { + sum += readSoilMoisture(); + delay(SAMPLE_DELAY_MS); // Small delay between samples + } + + int avg = (int)(sum / numSamples); + return avg; +} + +/** + * Enter deep sleep mode for the defined duration. + */ +void enterDeepSleep() { + Serial.println("Entering deep sleep for 1 hour..."); + + // Turn off LED to indicate sleep state + digitalWrite(LED_PIN, LOW); + + // Disable BLE before sleeping + BLEDevice::deinit(true); + + // Configure deep sleep wake-up time + esp_sleep_enable_timer_wakeup(DEEP_SLEEP_DURATION_US); + + // Enter deep sleep + esp_deep_sleep_start(); +} + +/** + * Handle calibration button presses to manage calibration steps. + */ +void handleCalibrationButtonPress() { + calibrationCount++; + + switch (calibrationCount) { + case 1: + // Calibration Step 1: Record Dry Value + Serial.println("Calibration Step 1: Place device in dry soil and press button."); + currentState = STATE_CALIBRATE_NOT_IN_SOIL; + break; + + case 2: + // Calibration Step 2: Record Wet Value + Serial.println("Calibration Step 2: Place device in wet soil and press button."); + currentState = STATE_CALIBRATE_IN_SOIL; + break; + + case 3: + // Calibration Complete + Serial.println("Calibration Complete. Returning to normal operation."); + currentState = STATE_NORMAL; + calibrationCount = 0; // Reset for next calibration + break; + + default: + // Reset Calibration + Serial.println("Calibration Reset. Returning to normal operation."); + currentState = STATE_NORMAL; + calibrationCount = 0; + break; + } + + updateLED(); +} + +/** + * Read calibration values from EEPROM. + */ +void readCalibrationValues() { + EEPROM.get(EEPROM_DRY_ADDRESS, dryValue); + EEPROM.get(EEPROM_WET_ADDRESS, wetValue); + + // Validate calibration values + if (dryValue == 0xFFFFFFFF || wetValue == 0xFFFFFFFF) { + dryValue = DRY_VALUE_DEFAULT; + wetValue = WET_VALUE_DEFAULT; + } +} + +/** + * Write calibration values to EEPROM. + */ +void writeCalibrationValues(int dry, int wet) { + EEPROM.put(EEPROM_DRY_ADDRESS, dry); + EEPROM.put(EEPROM_WET_ADDRESS, wet); + EEPROM.commit(); +} + +/** + * Check for button presses with debouncing. + */ +void checkButtonPress() { + bool reading = digitalRead(BUTTON_PIN); + + if (reading != lastButtonState) { + lastDebounceTime = millis(); + } + + if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) { + if (reading == LOW && lastButtonState == HIGH) { + buttonPressed = true; + } + } + + lastButtonState = reading; +} + +/** + * Update LED based on the current state. + */ +void updateLED() { + // Reset LED parameters + previousBlinkTime = millis(); + ledState = false; + + switch (currentState) { + case STATE_NORMAL: + // LED is handled by BLE connection callbacks + digitalWrite(LED_PIN, LOW); // Ensure LED is off in normal mode + break; + + case STATE_CALIBRATE_NOT_IN_SOIL: + // Solid LED to indicate calibration for dry soil + digitalWrite(LED_PIN, HIGH); + break; + + case STATE_CALIBRATE_IN_SOIL: + // Start blinking in 2:1 ratio for wet soil calibration + digitalWrite(LED_PIN, HIGH); // Start with LED ON + ledState = true; + break; + } +} + +/** + * Initialize EEPROM and read calibration values. + */ +void initializeEEPROM() { + if (!EEPROM.begin(EEPROM_SIZE)) { + Serial.println("Failed to initialize EEPROM"); + // Handle EEPROM initialization failure if necessary + } + readCalibrationValues(); +} + +/** + * Update mapping based on calibrated dry and wet values. + */ +int mapMoisture(int rawValue) { + int percent = map(rawValue, dryValue, wetValue, 0, 100); + return constrain(percent, 0, 100); +} + +// ======== Setup Function ======== +void setup() { + // Initialize Serial + Serial.begin(115200); + delay(1000); // Allow time for serial to initialize + + // Initialize LED pin + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); // LED off initially + + // Initialize Button pin + pinMode(BUTTON_PIN, INPUT_PULLUP); // Use INPUT_PULLUP if using internal resistor + // If using external resistor, use INPUT instead: + // pinMode(BUTTON_PIN, INPUT); + + // Initialize EEPROM + initializeEEPROM(); + Serial.printf("Calibrated Dry Value: %d\n", dryValue); + Serial.printf("Calibrated Wet Value: %d\n", wetValue); + + // Initialize BLE with deviceName variable + BLEDevice::init(deviceName); // Initialize BLE with the variable device name + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create BLE service + BLEService* pService = pServer->createService(SERVICE_UUID); + + // Create BLE characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY + ); + + // Add descriptor for notifications + pCharacteristic->addDescriptor(new BLE2902()); + + // Start the service + pService->start(); + + // Start advertising + pServer->getAdvertising()->addServiceUUID(SERVICE_UUID); + pServer->getAdvertising()->setScanResponse(true); + pServer->getAdvertising()->start(); + + Serial.println("BLE server is running..."); + + // Start connection timeout timer + connectionTimerStarted = true; + connectionStartTime = millis(); + + // Initialize state and LED + currentState = STATE_NORMAL; + calibrationCount = 0; + updateLED(); +} + +// ======== Loop Function ======== +void loop() { + // Check for button presses + checkButtonPress(); + + if (buttonPressed) { + buttonPressed = false; + if (currentState != STATE_NORMAL) { + // Handle calibration step + handleCalibrationButtonPress(); + } else { + // Initiate calibration + Serial.println("Button pressed. Initiating calibration mode."); + currentState = STATE_CALIBRATE_NOT_IN_SOIL; + calibrationCount = 1; + updateLED(); + } + } + + unsigned long currentTime = millis(); + + // ======== Connection Timeout Logic ======== + if (connectionTimerStarted) { + if (currentTime - connectionStartTime >= CONNECTION_TIMEOUT_MS) { + Serial.println("No connection within 2 minutes. Entering deep sleep."); + enterDeepSleep(); + } + } + + // ======== BLE Connected Logic ======== + if (isConnected && !dataSent && currentState == STATE_NORMAL) { + // Take sensor readings + int rawMoisture = analogRead(MOISTURE_PIN); + int moistureAvg = mapMoisture(rawMoisture); + Serial.printf("Averaged Soil Moisture: %d%%\n", moistureAvg); + + // Update the characteristic and notify the client + pCharacteristic->setValue(moistureAvg); + pCharacteristic->notify(); + + dataSent = true; + + // Immediately enter deep sleep after sending data + enterDeepSleep(); + } + + // ======== LED Update ======== + updateLED(); + + // ======== LED Blinking Logic ======== + if (currentState == STATE_CALIBRATE_IN_SOIL) { + if (millis() - previousBlinkTime >= (ledState ? BLINK_ON_DURATION : BLINK_OFF_DURATION)) { + ledState = !ledState; + digitalWrite(LED_PIN, ledState ? HIGH : LOW); + previousBlinkTime = millis(); + } + } + + // Short delay to prevent watchdog resets + delay(100); +}