Add new code for battery validation, updated probe node code added base read me

This commit is contained in:
programmingPug
2025-01-15 16:49:52 -05:00
parent f65ead854c
commit b015187916
28 changed files with 50671 additions and 413 deletions

3
Eagle/DESCRIPTION Normal file
View File

@@ -0,0 +1,3 @@
<b>User Projects</b>
<p>
This folder contains user Projects files.

1908
Eagle/soilSensor.b#1 Normal file

File diff suppressed because it is too large Load Diff

1938
Eagle/soilSensor.b#2 Normal file

File diff suppressed because it is too large Load Diff

1965
Eagle/soilSensor.b#3 Normal file

File diff suppressed because it is too large Load Diff

1963
Eagle/soilSensor.b#4 Normal file

File diff suppressed because it is too large Load Diff

1904
Eagle/soilSensor.b#5 Normal file

File diff suppressed because it is too large Load Diff

1952
Eagle/soilSensor.b#6 Normal file

File diff suppressed because it is too large Load Diff

1904
Eagle/soilSensor.b#7 Normal file

File diff suppressed because it is too large Load Diff

1940
Eagle/soilSensor.b#8 Normal file

File diff suppressed because it is too large Load Diff

1937
Eagle/soilSensor.b#9 Normal file

File diff suppressed because it is too large Load Diff

1965
Eagle/soilSensor.brd Normal file

File diff suppressed because it is too large Load Diff

25
Eagle/soilSensor.pro Normal file
View File

@@ -0,0 +1,25 @@
EAGLE AutoRouter Statistics:
Job : C:/Users/ckoch/Documents/EAGLE/projects/soilSensor.brd
Start at : 16:06:15 (1/15/2025)
End at : 16:06:22 (1/15/2025)
Elapsed time : 00:00:03
Signals : 11 RoutingGrid: 11 mil Layers: 2
Connections : 13 predefined: 2 ( 3 Vias )
Router memory : 107632
Passname : TopRouter Route Optimize1 Optimize2 Optimize3 Optimize4 Optimize5 Optimize6 Optimize7 Optimize8 Optimize9Optimize10Optimize11Optimize12
Time per pass : 00:00:02 00:00:00 00:00:00 00:00:00 00:00:01 00:00:00 00:00:00 00:00:00 00:00:00 00:00:00 00:00:00 00:00:00 00:00:00 00:00:00
Number of Ripups : 0 0 0 0 0 0 0 0 0 0 0 0 0 0
max. Level : 0 0 0 0 0 0 0 0 0 0 0 0 0 0
max. Total : 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Routed : 0 11 11 11 11 11 11 11 11 11 11 11 11 11
Vias : 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Resolution : 15.4 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 % 100.0 %
Final : 100.0% finished

5255
Eagle/soilSensor.s#1 Normal file

File diff suppressed because it is too large Load Diff

5255
Eagle/soilSensor.s#2 Normal file

File diff suppressed because it is too large Load Diff

5254
Eagle/soilSensor.s#3 Normal file

File diff suppressed because it is too large Load Diff

1699
Eagle/soilSensor.s#4 Normal file

File diff suppressed because it is too large Load Diff

1612
Eagle/soilSensor.s#5 Normal file

File diff suppressed because it is too large Load Diff

1636
Eagle/soilSensor.s#6 Normal file

File diff suppressed because it is too large Load Diff

1578
Eagle/soilSensor.s#7 Normal file

File diff suppressed because it is too large Load Diff

1530
Eagle/soilSensor.s#8 Normal file

File diff suppressed because it is too large Load Diff

1531
Eagle/soilSensor.s#9 Normal file

File diff suppressed because it is too large Load Diff

5245
Eagle/soilSensor.sch Normal file

File diff suppressed because it is too large Load Diff

275
Eagle/soilSensor/eagle.epf Normal file
View File

@@ -0,0 +1,275 @@
[Eagle]
Version="09 06 02"
Platform="Windows"
Globals="Globals"
Desktop="Desktop"
[Globals]
AutoSaveProject=1
UsedLibraryUrn="urn:adsk.eagle:library:79"
UsedLibraryUrn="urn:adsk.eagle:library:88"
UsedLibraryUrn="urn:adsk.eagle:library:201"
UsedLibraryUrn="urn:adsk.eagle:library:202"
UsedLibraryUrn="urn:adsk.eagle:library:203"
UsedLibraryUrn="urn:adsk.eagle:library:204"
UsedLibraryUrn="urn:adsk.eagle:library:206"
UsedLibraryUrn="urn:adsk.eagle:library:208"
UsedLibraryUrn="urn:adsk.eagle:library:210"
UsedLibraryUrn="urn:adsk.eagle:library:211"
UsedLibraryUrn="urn:adsk.eagle:library:212"
UsedLibraryUrn="urn:adsk.eagle:library:213"
UsedLibraryUrn="urn:adsk.eagle:library:89"
UsedLibraryUrn="urn:adsk.eagle:library:214"
UsedLibraryUrn="urn:adsk.eagle:library:215"
UsedLibraryUrn="urn:adsk.eagle:library:218"
UsedLibraryUrn="urn:adsk.eagle:library:220"
UsedLibraryUrn="urn:adsk.eagle:library:221"
UsedLibraryUrn="urn:adsk.eagle:library:222"
UsedLibraryUrn="urn:adsk.eagle:library:224"
UsedLibraryUrn="urn:adsk.eagle:library:226"
UsedLibraryUrn="urn:adsk.eagle:library:229"
UsedLibraryUrn="urn:adsk.eagle:library:230"
UsedLibraryUrn="urn:adsk.eagle:library:90"
UsedLibraryUrn="urn:adsk.eagle:library:231"
UsedLibraryUrn="urn:adsk.eagle:library:233"
UsedLibraryUrn="urn:adsk.eagle:library:234"
UsedLibraryUrn="urn:adsk.eagle:library:235"
UsedLibraryUrn="urn:adsk.eagle:library:236"
UsedLibraryUrn="urn:adsk.eagle:library:237"
UsedLibraryUrn="urn:adsk.eagle:library:238"
UsedLibraryUrn="urn:adsk.eagle:library:239"
UsedLibraryUrn="urn:adsk.eagle:library:240"
UsedLibraryUrn="urn:adsk.eagle:library:241"
UsedLibraryUrn="urn:adsk.eagle:library:93"
UsedLibraryUrn="urn:adsk.eagle:library:242"
UsedLibraryUrn="urn:adsk.eagle:library:243"
UsedLibraryUrn="urn:adsk.eagle:library:244"
UsedLibraryUrn="urn:adsk.eagle:library:245"
UsedLibraryUrn="urn:adsk.eagle:library:248"
UsedLibraryUrn="urn:adsk.eagle:library:250"
UsedLibraryUrn="urn:adsk.eagle:library:251"
UsedLibraryUrn="urn:adsk.eagle:library:252"
UsedLibraryUrn="urn:adsk.eagle:library:255"
UsedLibraryUrn="urn:adsk.eagle:library:256"
UsedLibraryUrn="urn:adsk.eagle:library:94"
UsedLibraryUrn="urn:adsk.eagle:library:258"
UsedLibraryUrn="urn:adsk.eagle:library:259"
UsedLibraryUrn="urn:adsk.eagle:library:260"
UsedLibraryUrn="urn:adsk.eagle:library:261"
UsedLibraryUrn="urn:adsk.eagle:library:262"
UsedLibraryUrn="urn:adsk.eagle:library:263"
UsedLibraryUrn="urn:adsk.eagle:library:264"
UsedLibraryUrn="urn:adsk.eagle:library:265"
UsedLibraryUrn="urn:adsk.eagle:library:266"
UsedLibraryUrn="urn:adsk.eagle:library:267"
UsedLibraryUrn="urn:adsk.eagle:library:96"
UsedLibraryUrn="urn:adsk.eagle:library:269"
UsedLibraryUrn="urn:adsk.eagle:library:270"
UsedLibraryUrn="urn:adsk.eagle:library:272"
UsedLibraryUrn="urn:adsk.eagle:library:273"
UsedLibraryUrn="urn:adsk.eagle:library:274"
UsedLibraryUrn="urn:adsk.eagle:library:275"
UsedLibraryUrn="urn:adsk.eagle:library:276"
UsedLibraryUrn="urn:adsk.eagle:library:278"
UsedLibraryUrn="urn:adsk.eagle:library:279"
UsedLibraryUrn="urn:adsk.eagle:library:281"
UsedLibraryUrn="urn:adsk.eagle:library:97"
UsedLibraryUrn="urn:adsk.eagle:library:283"
UsedLibraryUrn="urn:adsk.eagle:library:284"
UsedLibraryUrn="urn:adsk.eagle:library:285"
UsedLibraryUrn="urn:adsk.eagle:library:287"
UsedLibraryUrn="urn:adsk.eagle:library:288"
UsedLibraryUrn="urn:adsk.eagle:library:289"
UsedLibraryUrn="urn:adsk.eagle:library:290"
UsedLibraryUrn="urn:adsk.eagle:library:291"
UsedLibraryUrn="urn:adsk.eagle:library:293"
UsedLibraryUrn="urn:adsk.eagle:library:294"
UsedLibraryUrn="urn:adsk.eagle:library:98"
UsedLibraryUrn="urn:adsk.eagle:library:296"
UsedLibraryUrn="urn:adsk.eagle:library:297"
UsedLibraryUrn="urn:adsk.eagle:library:298"
UsedLibraryUrn="urn:adsk.eagle:library:300"
UsedLibraryUrn="urn:adsk.eagle:library:302"
UsedLibraryUrn="urn:adsk.eagle:library:303"
UsedLibraryUrn="urn:adsk.eagle:library:304"
UsedLibraryUrn="urn:adsk.eagle:library:307"
UsedLibraryUrn="urn:adsk.eagle:library:312"
UsedLibraryUrn="urn:adsk.eagle:library:313"
UsedLibraryUrn="urn:adsk.eagle:library:99"
UsedLibraryUrn="urn:adsk.eagle:library:314"
UsedLibraryUrn="urn:adsk.eagle:library:315"
UsedLibraryUrn="urn:adsk.eagle:library:317"
UsedLibraryUrn="urn:adsk.eagle:library:318"
UsedLibraryUrn="urn:adsk.eagle:library:319"
UsedLibraryUrn="urn:adsk.eagle:library:320"
UsedLibraryUrn="urn:adsk.eagle:library:322"
UsedLibraryUrn="urn:adsk.eagle:library:323"
UsedLibraryUrn="urn:adsk.eagle:library:324"
UsedLibraryUrn="urn:adsk.eagle:library:325"
UsedLibraryUrn="urn:adsk.eagle:library:102"
UsedLibraryUrn="urn:adsk.eagle:library:327"
UsedLibraryUrn="urn:adsk.eagle:library:328"
UsedLibraryUrn="urn:adsk.eagle:library:331"
UsedLibraryUrn="urn:adsk.eagle:library:332"
UsedLibraryUrn="urn:adsk.eagle:library:334"
UsedLibraryUrn="urn:adsk.eagle:library:335"
UsedLibraryUrn="urn:adsk.eagle:library:336"
UsedLibraryUrn="urn:adsk.eagle:library:337"
UsedLibraryUrn="urn:adsk.eagle:library:338"
UsedLibraryUrn="urn:adsk.eagle:library:339"
UsedLibraryUrn="urn:adsk.eagle:library:80"
UsedLibraryUrn="urn:adsk.eagle:library:105"
UsedLibraryUrn="urn:adsk.eagle:library:341"
UsedLibraryUrn="urn:adsk.eagle:library:342"
UsedLibraryUrn="urn:adsk.eagle:library:343"
UsedLibraryUrn="urn:adsk.eagle:library:344"
UsedLibraryUrn="urn:adsk.eagle:library:346"
UsedLibraryUrn="urn:adsk.eagle:library:347"
UsedLibraryUrn="urn:adsk.eagle:library:350"
UsedLibraryUrn="urn:adsk.eagle:library:351"
UsedLibraryUrn="urn:adsk.eagle:library:353"
UsedLibraryUrn="urn:adsk.eagle:library:358"
UsedLibraryUrn="urn:adsk.eagle:library:107"
UsedLibraryUrn="urn:adsk.eagle:library:360"
UsedLibraryUrn="urn:adsk.eagle:library:361"
UsedLibraryUrn="urn:adsk.eagle:library:362"
UsedLibraryUrn="urn:adsk.eagle:library:364"
UsedLibraryUrn="urn:adsk.eagle:library:366"
UsedLibraryUrn="urn:adsk.eagle:library:367"
UsedLibraryUrn="urn:adsk.eagle:library:368"
UsedLibraryUrn="urn:adsk.eagle:library:369"
UsedLibraryUrn="urn:adsk.eagle:library:371"
UsedLibraryUrn="urn:adsk.eagle:library:372"
UsedLibraryUrn="urn:adsk.eagle:library:109"
UsedLibraryUrn="urn:adsk.eagle:library:373"
UsedLibraryUrn="urn:adsk.eagle:library:374"
UsedLibraryUrn="urn:adsk.eagle:library:375"
UsedLibraryUrn="urn:adsk.eagle:library:376"
UsedLibraryUrn="urn:adsk.eagle:library:377"
UsedLibraryUrn="urn:adsk.eagle:library:378"
UsedLibraryUrn="urn:adsk.eagle:library:379"
UsedLibraryUrn="urn:adsk.eagle:library:380"
UsedLibraryUrn="urn:adsk.eagle:library:382"
UsedLibraryUrn="urn:adsk.eagle:library:383"
UsedLibraryUrn="urn:adsk.eagle:library:111"
UsedLibraryUrn="urn:adsk.eagle:library:385"
UsedLibraryUrn="urn:adsk.eagle:library:386"
UsedLibraryUrn="urn:adsk.eagle:library:387"
UsedLibraryUrn="urn:adsk.eagle:library:388"
UsedLibraryUrn="urn:adsk.eagle:library:389"
UsedLibraryUrn="urn:adsk.eagle:library:391"
UsedLibraryUrn="urn:adsk.eagle:library:392"
UsedLibraryUrn="urn:adsk.eagle:library:393"
UsedLibraryUrn="urn:adsk.eagle:library:394"
UsedLibraryUrn="urn:adsk.eagle:library:396"
UsedLibraryUrn="urn:adsk.eagle:library:112"
UsedLibraryUrn="urn:adsk.eagle:library:397"
UsedLibraryUrn="urn:adsk.eagle:library:398"
UsedLibraryUrn="urn:adsk.eagle:library:399"
UsedLibraryUrn="urn:adsk.eagle:library:400"
UsedLibraryUrn="urn:adsk.eagle:library:401"
UsedLibraryUrn="urn:adsk.eagle:library:402"
UsedLibraryUrn="urn:adsk.eagle:library:403"
UsedLibraryUrn="urn:adsk.eagle:library:404"
UsedLibraryUrn="urn:adsk.eagle:library:407"
UsedLibraryUrn="urn:adsk.eagle:library:408"
UsedLibraryUrn="urn:adsk.eagle:library:113"
UsedLibraryUrn="urn:adsk.eagle:library:409"
UsedLibraryUrn="urn:adsk.eagle:library:410"
UsedLibraryUrn="urn:adsk.eagle:library:411"
UsedLibraryUrn="urn:adsk.eagle:library:412"
UsedLibraryUrn="urn:adsk.eagle:library:417"
UsedLibraryUrn="urn:adsk.eagle:library:418"
UsedLibraryUrn="urn:adsk.eagle:library:419"
UsedLibraryUrn="urn:adsk.eagle:library:527439"
UsedLibraryUrn="urn:adsk.eagle:library:5610454"
UsedLibraryUrn="urn:adsk.eagle:library:10015156"
UsedLibraryUrn="urn:adsk.eagle:library:116"
UsedLibraryUrn="urn:adsk.eagle:library:117"
UsedLibraryUrn="urn:adsk.eagle:library:118"
UsedLibraryUrn="urn:adsk.eagle:library:119"
UsedLibraryUrn="urn:adsk.eagle:library:81"
UsedLibraryUrn="urn:adsk.eagle:library:121"
UsedLibraryUrn="urn:adsk.eagle:library:122"
UsedLibraryUrn="urn:adsk.eagle:library:123"
UsedLibraryUrn="urn:adsk.eagle:library:124"
UsedLibraryUrn="urn:adsk.eagle:library:125"
UsedLibraryUrn="urn:adsk.eagle:library:126"
UsedLibraryUrn="urn:adsk.eagle:library:127"
UsedLibraryUrn="urn:adsk.eagle:library:128"
UsedLibraryUrn="urn:adsk.eagle:library:129"
UsedLibraryUrn="urn:adsk.eagle:library:130"
UsedLibraryUrn="urn:adsk.eagle:library:82"
UsedLibraryUrn="urn:adsk.eagle:library:132"
UsedLibraryUrn="urn:adsk.eagle:library:133"
UsedLibraryUrn="urn:adsk.eagle:library:134"
UsedLibraryUrn="urn:adsk.eagle:library:135"
UsedLibraryUrn="urn:adsk.eagle:library:136"
UsedLibraryUrn="urn:adsk.eagle:library:138"
UsedLibraryUrn="urn:adsk.eagle:library:139"
UsedLibraryUrn="urn:adsk.eagle:library:140"
UsedLibraryUrn="urn:adsk.eagle:library:141"
UsedLibraryUrn="urn:adsk.eagle:library:143"
UsedLibraryUrn="urn:adsk.eagle:library:83"
UsedLibraryUrn="urn:adsk.eagle:library:144"
UsedLibraryUrn="urn:adsk.eagle:library:145"
UsedLibraryUrn="urn:adsk.eagle:library:147"
UsedLibraryUrn="urn:adsk.eagle:library:148"
UsedLibraryUrn="urn:adsk.eagle:library:149"
UsedLibraryUrn="urn:adsk.eagle:library:150"
UsedLibraryUrn="urn:adsk.eagle:library:151"
UsedLibraryUrn="urn:adsk.eagle:library:152"
UsedLibraryUrn="urn:adsk.eagle:library:153"
UsedLibraryUrn="urn:adsk.eagle:library:154"
UsedLibraryUrn="urn:adsk.eagle:library:84"
UsedLibraryUrn="urn:adsk.eagle:library:155"
UsedLibraryUrn="urn:adsk.eagle:library:156"
UsedLibraryUrn="urn:adsk.eagle:library:157"
UsedLibraryUrn="urn:adsk.eagle:library:158"
UsedLibraryUrn="urn:adsk.eagle:library:159"
UsedLibraryUrn="urn:adsk.eagle:library:160"
UsedLibraryUrn="urn:adsk.eagle:library:161"
UsedLibraryUrn="urn:adsk.eagle:library:162"
UsedLibraryUrn="urn:adsk.eagle:library:163"
UsedLibraryUrn="urn:adsk.eagle:library:164"
UsedLibraryUrn="urn:adsk.eagle:library:85"
UsedLibraryUrn="urn:adsk.eagle:library:165"
UsedLibraryUrn="urn:adsk.eagle:library:166"
UsedLibraryUrn="urn:adsk.eagle:library:167"
UsedLibraryUrn="urn:adsk.eagle:library:169"
UsedLibraryUrn="urn:adsk.eagle:library:170"
UsedLibraryUrn="urn:adsk.eagle:library:171"
UsedLibraryUrn="urn:adsk.eagle:library:172"
UsedLibraryUrn="urn:adsk.eagle:library:173"
UsedLibraryUrn="urn:adsk.eagle:library:174"
UsedLibraryUrn="urn:adsk.eagle:library:175"
UsedLibraryUrn="urn:adsk.eagle:library:86"
UsedLibraryUrn="urn:adsk.eagle:library:176"
UsedLibraryUrn="urn:adsk.eagle:library:177"
UsedLibraryUrn="urn:adsk.eagle:library:179"
UsedLibraryUrn="urn:adsk.eagle:library:180"
UsedLibraryUrn="urn:adsk.eagle:library:181"
UsedLibraryUrn="urn:adsk.eagle:library:182"
UsedLibraryUrn="urn:adsk.eagle:library:183"
UsedLibraryUrn="urn:adsk.eagle:library:184"
UsedLibraryUrn="urn:adsk.eagle:library:188"
UsedLibraryUrn="urn:adsk.eagle:library:189"
UsedLibraryUrn="urn:adsk.eagle:library:87"
UsedLibraryUrn="urn:adsk.eagle:library:190"
UsedLibraryUrn="urn:adsk.eagle:library:191"
UsedLibraryUrn="urn:adsk.eagle:library:192"
UsedLibraryUrn="urn:adsk.eagle:library:193"
UsedLibraryUrn="urn:adsk.eagle:library:194"
UsedLibraryUrn="urn:adsk.eagle:library:195"
UsedLibraryUrn="urn:adsk.eagle:library:196"
UsedLibraryUrn="urn:adsk.eagle:library:197"
UsedLibraryUrn="urn:adsk.eagle:library:198"
UsedLibraryUrn="urn:adsk.eagle:library:199"
[Win_1]
Type="Control Panel"
Number=0
[Desktop]
Screen="5120 1440"
Window="Win_1"

9
README.txt Normal file
View File

@@ -0,0 +1,9 @@
building and deploying:
docker build -t houseplant-client:latest .
docker image tag houseplant-client 192.168.1.193:5000/houseplant-client
docker push 192.168.1.193:5000/houseplant-client
docker build --no-cache -t houseplant-api:latest .
docker image tag houseplant-api 192.168.1.193:5000/houseplant-api
docker push 192.168.1.193:5000/houseplant-api

View File

@@ -0,0 +1,61 @@
// XAIO-ESP32-C6 Battery Monitor with Averaged ADC Readings
// Measures the battery voltage via a resistor divider (two 220 kΩ resistors),
// averages 10 ADC readings, and reports the battery percentage.
// Battery assumed: single-cell LiPo with 3.0V (0%) to 4.2V (100%).
// ----- CONFIGURATION -----
const int analogPin = A0; // ADC pin connected to the resistor divider
const float adcRefVoltage = 3.3; // ADC reference voltage (Volts)
const int adcMaxValue = 4095; // Maximum ADC value (for 12-bit resolution)
const float voltageDividerFactor = 2.0; // Divider factor: battery voltage is divided by 2
// Battery voltage range settings (adjust as necessary)
const float batteryMinVoltage = 3.0; // Voltage corresponding to 0% battery
const float batteryMaxVoltage = 4.2; // Voltage corresponding to 100% battery
const int numSamples = 10; // Number of ADC samples to average
// ----- SETUP -----
void setup() {
Serial.begin(115200);
// If needed, additional ADC calibration or configuration can be added here
}
// ----- MAIN LOOP -----
void loop() {
long totalADC = 0;
// Take numSamples readings
for (int i = 0; i < numSamples; i++) {
totalADC += analogRead(analogPin);
delay(10); // Short delay between samples (adjust as needed)
}
// Calculate the average ADC value
float avgADC = totalADC / (float)numSamples;
// Convert the averaged ADC value to the measured voltage at the divider
float measuredVoltage = (avgADC / adcMaxValue) * adcRefVoltage;
// Calculate the actual battery voltage using the divider factor
float batteryVoltage = measuredVoltage * voltageDividerFactor;
// Map the battery voltage to a battery percentage using a linear mapping
float batteryPercent = ((batteryVoltage - batteryMinVoltage) / (batteryMaxVoltage - batteryMinVoltage)) * 100.0;
if (batteryPercent > 100.0) batteryPercent = 100.0;
if (batteryPercent < 0.0) batteryPercent = 0.0;
// Print the results to the Serial Monitor
Serial.print("Avg ADC Value: ");
Serial.print(avgADC, 2);
Serial.print(" | Divider Voltage: ");
Serial.print(measuredVoltage, 2);
Serial.print(" V | Battery Voltage: ");
Serial.print(batteryVoltage, 2);
Serial.print(" V | Battery Percent: ");
Serial.print(batteryPercent, 1);
Serial.println("%");
// Wait 2 seconds before the next reading
delay(2000);
}

View File

@@ -1,8 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
]
}

View File

@@ -1,405 +0,0 @@
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <EEPROM.h>
#include <esp_sleep.h>
// ======== 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 (01023)
// ======== 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 (0100%).
*/
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);
}

View File

@@ -0,0 +1,327 @@
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <EEPROM.h>
#include "esp_sleep.h"
// ***** Configuration *****
// Pins:
#define BATTERY_PIN A0 // Battery voltage reading via a resistor divider
#define SOIL_PIN A2 // Soil moisture sensor analog reading
#define BUTTON_PIN D1 // Button on D1, active LOW (internal pull-up)
// EEPROM settings: 4 bytes (2 for dry, 2 for wet)
#define EEPROM_SIZE 4
#define EEPROM_DRY_ADDR 0
#define EEPROM_WET_ADDR 2
// Sampling settings:
#define BATTERY_SAMPLES 10
#define SOIL_SAMPLES 20
#define SAMPLE_INTERVAL 100 // 0.1 seconds between soil samples
#define NORMAL_DELAY 2000 // 2 seconds delay between normal readings
#define CAL_DELAY 10000 // 10-second delay before calibration reading
// Deep sleep settings:
#define SLEEP_DURATION_HOURS 6
#define AWAKE_MAX_MS 300000 // 5 minutes in milliseconds
// Battery voltage parameters (assuming 220kΩ divider, two equal resistors)
#define ADC_MAX 4095.0
#define VREF 3.3
#define VOLTAGE_DIVIDER_FACTOR 2.0
#define BATTERY_FULL 4.2
#define BATTERY_EMPTY 3.0
// ***** BLE Settings *****
#define SERVICE_UUID "12345678-1234-1234-1234-123456789abc"
#define MOISTURE_CHAR_UUID "abcd1234-5678-1234-5678-abcdef123456"
#define BATTERY_CHAR_UUID "fedcba98-7654-3210-0123-456789abcdef"
// ***** Default Calibration Values *****
// These defaults are used if no valid calibration exists in EEPROM.
#define DEFAULT_DRY_CAL 3000 // Higher raw reading for dry soil
#define DEFAULT_WET_CAL 1000 // Lower raw reading for wet soil
// ***** Global Calibration Variables *****
int dryCal = 0;
int wetCal = 0;
// Calibration mode states
enum CalState {
CAL_OFF,
CAL_DRY_PENDING,
CAL_WET_PENDING,
CAL_DONE
};
CalState calState = CAL_OFF;
// Debounce for button on BUTTON_PIN
bool buttonPressed = false;
unsigned long lastButtonPress = 0;
const unsigned long debounceDelay = 50; // 50 ms
// Operation mode: NORMAL_MODE or CALIBRATION_MODE
enum OperationMode {
NORMAL_MODE,
CALIBRATION_MODE
};
OperationMode opMode = NORMAL_MODE;
// ***** BLE Server Variables *****
BLEServer* pServer = nullptr;
BLECharacteristic* pMoistureCharacteristic = nullptr;
BLECharacteristic* pBatteryCharacteristic = nullptr;
// ***** Time Tracking *****
unsigned long awakeStartTime = 0;
// ***** Helper Functions *****
// Read battery voltage: average BATTERY_SAMPLES, convert reading to voltage, then map to a percentage.
float readBatteryVoltage() {
long total = 0;
for (int i = 0; i < BATTERY_SAMPLES; i++) {
total += analogRead(BATTERY_PIN);
delay(10);
}
float avg = total / (float)BATTERY_SAMPLES;
float v_adc = (avg * VREF) / ADC_MAX;
float v_bat = v_adc * VOLTAGE_DIVIDER_FACTOR;
return v_bat;
}
int batteryPercentage(float voltage) {
int percent = (int)(((voltage - BATTERY_EMPTY) / (BATTERY_FULL - BATTERY_EMPTY)) * 100);
if (percent < 0) percent = 0;
if (percent > 100) percent = 100;
return percent;
}
// Read soil moisture raw value: average SOIL_SAMPLES spaced by SAMPLE_INTERVAL ms.
int readSoilRaw() {
long sum = 0;
for (int i = 0; i < SOIL_SAMPLES; i++) {
sum += analogRead(SOIL_PIN);
delay(SAMPLE_INTERVAL);
}
return sum / SOIL_SAMPLES;
}
// Map raw soil moisture value to percentage using calibration values.
int mapSoilMoisture(int raw) {
if (dryCal == wetCal) return 0;
int percent = map(raw, dryCal, wetCal, 0, 100);
percent = constrain(percent, 0, 100);
return percent;
}
// Update BLE characteristics with current battery and soil moisture data.
void updateBLE() {
int soilRaw = readSoilRaw();
int moisturePercent = 0;
if (dryCal != 0 && wetCal != 0 && dryCal > wetCal) {
moisturePercent = mapSoilMoisture(soilRaw);
}
float v_bat = readBatteryVoltage();
int batPercent = batteryPercentage(v_bat);
String moistureStr = String(moisturePercent);
String batteryStr = String(batPercent);
pMoistureCharacteristic->setValue(moistureStr.c_str());
pMoistureCharacteristic->notify();
pBatteryCharacteristic->setValue(batteryStr.c_str());
pBatteryCharacteristic->notify();
Serial.print("Battery: ");
Serial.print(v_bat);
Serial.print(" V (");
Serial.print(batPercent);
Serial.println("%)");
Serial.print("Soil raw: ");
Serial.print(soilRaw);
Serial.print(" Moisture: ");
Serial.print(moisturePercent);
Serial.println("%");
}
// Enter deep sleep for SLEEP_DURATION_HOURS hours.
void enterDeepSleep() {
Serial.println("Entering deep sleep.");
uint64_t sleepTimeUs = SLEEP_DURATION_HOURS * 3600ULL * 1000000ULL;
esp_deep_sleep(sleepTimeUs);
}
// ----- Calibration Functions -----
// Wait CAL_DELAY ms then take SOIL_SAMPLES samples and return the average raw value.
int calibrateSensor() {
delay(CAL_DELAY);
return readSoilRaw();
}
// Check the state of the calibration button on BUTTON_PIN and update calibration state.
void checkButton() {
if (digitalRead(BUTTON_PIN) == LOW) { // Button pressed
if (!buttonPressed && (millis() - lastButtonPress > debounceDelay)) {
buttonPressed = true;
lastButtonPress = millis();
// Cycle through calibration states
if (calState == CAL_OFF) {
calState = CAL_DRY_PENDING;
Serial.println("Button pressed: Entering Dry Calibration Mode. Ensure sensor is in dry soil.");
} else if (calState == CAL_DRY_PENDING) {
calState = CAL_WET_PENDING;
Serial.println("Button pressed: Entering Wet Calibration Mode. Place sensor in wet soil.");
} else if (calState == CAL_WET_PENDING) {
calState = CAL_DONE;
Serial.println("Button pressed: Finalizing Calibration.");
}
}
} else {
buttonPressed = false;
}
}
// Runs the calibration sequence.
// In Dry mode: after delay, take sample and assign dryCal.
// In Wet mode: similarly assign wetCal.
void runCalibration() {
if (calState == CAL_DRY_PENDING) {
Serial.println("Starting Dry Calibration...");
int dryValue = calibrateSensor();
dryCal = dryValue;
Serial.print("Dry calibration value: ");
Serial.println(dryCal);
// Wait for next button press to proceed to wet calibration.
while (calState == CAL_DRY_PENDING) {
checkButton();
delay(100);
}
}
if (calState == CAL_WET_PENDING) {
Serial.println("Starting Wet Calibration...");
int wetValue = calibrateSensor();
wetCal = wetValue;
Serial.print("Wet calibration value: ");
Serial.println(wetCal);
while (calState == CAL_WET_PENDING) {
checkButton();
delay(100);
}
}
if (calState == CAL_DONE) {
// Save calibration values to EEPROM
EEPROM.write(EEPROM_DRY_ADDR, lowByte(dryCal));
EEPROM.write(EEPROM_DRY_ADDR + 1, highByte(dryCal));
EEPROM.write(EEPROM_WET_ADDR, lowByte(wetCal));
EEPROM.write(EEPROM_WET_ADDR + 1, highByte(wetCal));
EEPROM.commit();
Serial.println("Calibration saved. Exiting calibration mode.");
calState = CAL_OFF;
opMode = NORMAL_MODE;
}
}
// ----- BLE Server Setup -----
void setupBLEServer() {
BLEDevice::init("SoilSensor_XIAO");
pServer = BLEDevice::createServer();
BLEService* pService = pServer->createService(SERVICE_UUID);
pMoistureCharacteristic = pService->createCharacteristic(
MOISTURE_CHAR_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
);
pMoistureCharacteristic->setValue("0");
pBatteryCharacteristic = pService->createCharacteristic(
BATTERY_CHAR_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
);
pBatteryCharacteristic->setValue("0");
pService->start();
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE Server started and advertising.");
}
// ----- Main Setup and Loop -----
void setup() {
Serial.begin(115200);
Serial.println("Booting Sensor Node...");
EEPROM.begin(EEPROM_SIZE);
pinMode(BATTERY_PIN, INPUT);
pinMode(SOIL_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Initialize BLE Server so sensor values can be advertised
setupBLEServer();
// Load calibration from EEPROM
int storedDry = EEPROM.read(EEPROM_DRY_ADDR) | (EEPROM.read(EEPROM_DRY_ADDR + 1) << 8);
int storedWet = EEPROM.read(EEPROM_WET_ADDR) | (EEPROM.read(EEPROM_WET_ADDR + 1) << 8);
// Check validity: dry value should be higher than wet value and nonzero.
if (storedDry > storedWet && storedDry > 0 && storedWet > 0) {
dryCal = storedDry;
wetCal = storedWet;
Serial.print("Loaded Calibration - Dry: ");
Serial.print(dryCal);
Serial.print(" Wet: ");
Serial.println(wetCal);
opMode = NORMAL_MODE;
} else {
// Use default calibration values if none are valid.
dryCal = DEFAULT_DRY_CAL;
wetCal = DEFAULT_WET_CAL;
Serial.println("No valid calibration found. Using default calibration values.");
Serial.print("Default Dry: ");
Serial.print(dryCal);
Serial.print(" Default Wet: ");
Serial.println(wetCal);
opMode = NORMAL_MODE; // Allow device to be usable even without calibration.
}
awakeStartTime = millis();
}
void loop() {
// Check for button press to trigger calibration mode.
checkButton();
if (digitalRead(BUTTON_PIN) == LOW && opMode == NORMAL_MODE) {
// If button is pressed while in normal mode, enter calibration mode.
Serial.println("Button pressed while awake. Entering Calibration Mode.");
opMode = CALIBRATION_MODE;
calState = CAL_DRY_PENDING;
}
if (opMode == CALIBRATION_MODE) {
runCalibration();
awakeStartTime = millis(); // reset awake time after calibration
return;
}
// NORMAL MODE
updateBLE();
// If more than 5 minutes have elapsed, go to deep sleep for 6 hours.
if (millis() - awakeStartTime >= AWAKE_MAX_MS) {
Serial.println("5 minutes elapsed; going to deep sleep.");
enterDeepSleep();
}
delay(NORMAL_DELAY);
}