Skip to content

Commit fcd866a

Browse files
committed
Lots of updates
- Can have muiltiple sensors - Added OTAP support (which surprisingly works perfectly) - Tried to deal with moisture sensor unreliability - Added automatic calibration I forgot to sync the public version of this code based on the private one. I actually haven't touched at it since August 2019, ie more than 2 years ago.
1 parent dc95c94 commit fcd866a

File tree

2 files changed

+192
-53
lines changed

2 files changed

+192
-53
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
## The board and project
44
The project and the board have been started by [lucafabri](https://github.com/lucafabbri/HiGrow-Arduino-Esp) who seems to have moved to other things since then.
55

6+
The current program is very stable.
7+
68
It can be bought on:
79
- [amazon](https://www.amazon.com/dp/B07J9LRJ4T/): This is the board I used
810
- [aliexpress](https://www.aliexpress.com/i/32969456777.html)
911

1012
## What this program does
1113
- Connect to one of the known wifi networks
1214
- Read the soil moisture sensor
13-
- Send the sensor value on the eedomus home automation cloud. This part be easily replaced by an other home auomation solution.
15+
- Automatically calibrates its sensor for min/max values
16+
- Fetch the settings associated with each device
17+
- Send the sensor value on the [eedomus](http://www.eedomus.com) home automation cloud. This part be easily replaced by an other home auomation solution.
1418

1519
## Known issues
1620
- Light reading doesn't work, it seems to be an hardware issue

higrow-esp32.ino

Lines changed: 187 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
#include <WiFiMulti.h>
44
#include <HTTPClient.h>
55
#include <ESP.h>
6+
#include <Preferences.h>
7+
#include <WiFiClient.h>
8+
#include <HTTPUpdate.h>
69

710
// This is inspired from https://github.com/lucafabbri/HiGrow-Arduino-Esp
811

9-
#define VERSION "0.1"
12+
#define VERSION_MAJOR 0
13+
#define VERSION_MINOR 2
14+
#define VERSION_PATCH 4
15+
#define VERSION_INT (VERSION_MAJOR*10000+VERSION_MINOR*100+VERSION_PATCH)
1016

1117
// Pin layout
1218
#define PIN_LED_BLUE 16
@@ -19,11 +25,14 @@
1925

2026
// For secrets
2127
#define EEDOMUS_API_SECRET "********"
28+
#define OTAP_URL_SECRET "********"
2229
#define WIFI_PASSWORD_1 "********"
2330
#define WIFI_PASSWORD_2 "********"
2431
#define WIFI_PASSWORD_3 "********"
2532
#define WIFI_PASSWORD_4 "********"
2633

34+
#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
35+
2736
void pinsSetup() {
2837
pinMode(PIN_POWER, INPUT); // No idea what this does
2938

@@ -36,14 +45,43 @@ void pinsSetup() {
3645
void serialSetup() {
3746
Serial.begin(115200);
3847
delay(10);
39-
Serial.printf("Wifi Soil Moisture Sensor (%s / %s %s)\n", VERSION, __DATE__, __TIME__);
48+
Serial.printf("Wifi Soil Moisture Sensor v%d.%d.%d (%s %s)\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, __DATE__, __TIME__);
4049
}
4150

42-
char deviceid[21] = "";
51+
typedef struct {
52+
String name;
53+
String macAddr;
54+
int deviceIdSoil;
55+
int deviceIdAirHumidity;
56+
int deviceIdAirTemperature;
57+
} t_device_ids;
58+
59+
// This is where I list all my sensors
60+
t_device_ids ids[] = {
61+
{ .name = "C1", .macAddr = "30:AE:A4:F4:C9:2C", .deviceIdSoil = 1579253 },
62+
{ .name = "C2", .macAddr = "CC:50:E3:A8:6B:64", .deviceIdSoil = 1579248 },
63+
{ .name = "C3", .macAddr = "CC:50:E3:A8:72:EC", .deviceIdSoil = 1579249 },
64+
{ .name = "C4", .macAddr = "30:AE:A4:F3:48:80", .deviceIdSoil = 1579250 },
65+
{ .name = "C5", .macAddr = "CC:50:E3:A8:97:54", .deviceIdSoil = 1584398 },
66+
{ .name = "C6", .macAddr = "CC:50:E3:A8:6E:48", .deviceIdSoil = 1579254 }
67+
};
68+
69+
t_device_ids * current_device_ids = NULL;
70+
Preferences preferences;
71+
72+
// char deviceid[21] = "";
4373
void settingsSetup() {
44-
sprintf(deviceid, "%" PRIu64, ESP.getEfuseMac());
45-
Serial.print("DeviceId: ");
46-
Serial.println(deviceid);
74+
String macAddress = WiFi.macAddress();
75+
for ( int i = 0; i < NELEMS(ids); i++ ) {
76+
t_device_ids * dev_id = & ids[i];
77+
if ( dev_id->macAddr == macAddress ) {
78+
current_device_ids = dev_id;
79+
Serial.print("Device identified: " + dev_id->name + "\n" );
80+
return;
81+
}
82+
}
83+
Serial.print("We don't know this device (" + macAddress + ")\n");
84+
sleepGo();
4785
}
4886

4987
WiFiMulti wifiMulti;
@@ -56,22 +94,20 @@ void wifiSetAPs() {
5694
}
5795

5896
void wifiConnect() {
59-
Serial.print(String("MAC address: ")+WiFi.macAddress());
60-
Serial.println();
61-
62-
WiFi.setHostname("wifi_gms");
63-
64-
Serial.print("Connecting Wifi...");
65-
WiFi.mode(WIFI_STA);
66-
while (wifiMulti.run() != WL_CONNECTED) {
67-
Serial.print(".");
68-
delay(500);
69-
}
70-
Serial.println("");
71-
Serial.println(String("WiFi connected to \"")+WiFi.SSID()+"\"");
97+
Serial.print("Connecting Wifi...");
98+
if ( current_device_ids ) {
99+
WiFi.setHostname(current_device_ids->name.c_str());
100+
}
101+
WiFi.enableSTA(true);
102+
while (wifiMulti.run() != WL_CONNECTED) {
103+
Serial.print(".");
104+
delay(500);
105+
}
106+
Serial.println("");
107+
Serial.println(String("WiFi connected to \"") + WiFi.SSID() + "\"");
72108

73-
Serial.print("IP address: ");
74-
Serial.println(WiFi.localIP());
109+
Serial.print("IP address: ");
110+
Serial.println(WiFi.localIP());
75111
}
76112

77113
void wifiSetup() {
@@ -80,17 +116,17 @@ void wifiSetup() {
80116
}
81117

82118
// TODO: The whole coming back from sleep logic is broken (bot for the RTC-attached memory and wake up reason). I have no idea of why.
83-
void sleepWakeUpReason(){
119+
void sleepWakeUpReason() {
84120
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
85121

86-
switch(wakeup_reason) {
122+
switch (wakeup_reason) {
87123
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
88124
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
89125
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
90126
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
91127
case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
92128
case 0: break;
93-
default : Serial.printf("Unknwon wake up reason: %d\n",wakeup_reason); break;
129+
default : Serial.printf("Unknwon wake up reason: %d\n", wakeup_reason); break;
94130
}
95131
}
96132

@@ -121,9 +157,9 @@ void IRAM_ATTR watchdogReset() {
121157

122158
void watchdogSetup() {
123159
watchdogTimer = timerBegin(0, 80, true);
124-
// It shouldn't take more than 1 minute to run the program
160+
// It shouldn't take more than 5 minute to run or even reflash the program
125161
timerAttachInterrupt(watchdogTimer, &watchdogReset, true);
126-
timerAlarmWrite(watchdogTimer, 1L * 60 * 1000 * 1000, false);
162+
timerAlarmWrite(watchdogTimer, 5L * 60 * 1000 * 1000, false);
127163
timerAlarmEnable(watchdogTimer);
128164
}
129165

@@ -132,33 +168,107 @@ void watchdogDisable() {
132168
timerEnd(watchdogTimer);
133169
}
134170

135-
void setup() {
136-
serialSetup();
171+
void otapSetup() {
172+
const int r = esp_random() % 1;
173+
if ( r != 0 ) {
174+
Serial.println("OTAP: Skipping it this time...");
175+
return;
176+
}
177+
178+
{
179+
HTTPClient client;
180+
client.begin(String(OTAP_URL_SECRET) + "version.txt");
181+
int status = client.GET();
182+
if (status != 200) {
183+
Serial.printf("OTAP: Wrong HTTP status: %d\n", status);
184+
return;
185+
}
186+
int version = client.getString().toInt();
187+
if ( version <= VERSION_INT ) {
188+
Serial.printf("OTAP: No update available (%d <= %d)\n", version, VERSION_INT);
189+
return;
190+
}
191+
Serial.printf("OTAP: Updating from %d to %d\n", VERSION_INT, version);
192+
}
193+
{
194+
Serial.println("OTAP: Starting update");
195+
WiFiClient client;
196+
197+
// The line below is optional. It can be used to blink the LED on the board during flashing
198+
// The LED will be on during download of one buffer of data from the network. The LED will
199+
// be off during writing that buffer to flash
200+
// On a good connection the LED should flash regularly. On a bad connection the LED will be
201+
// on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second
202+
// value is used to put the LED on. If the LED is on with HIGH, that value should be passed
203+
// httpUpdate.setLedPin(LED_BUILTIN, LOW);
204+
205+
t_httpUpdate_return ret = httpUpdate.update(client, String(OTAP_URL_SECRET) + "file.bin");
206+
207+
switch (ret) {
208+
case HTTP_UPDATE_FAILED:
209+
Serial.printf("OTAP: Failed: Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
210+
break;
211+
212+
case HTTP_UPDATE_NO_UPDATES:
213+
Serial.println("OTAP: No update");
214+
break;
215+
216+
case HTTP_UPDATE_OK:
217+
Serial.println("OTAP: OK");
218+
break;
219+
}
220+
}
221+
}
137222

138-
// We need a watchdog because the WifiMulti module is extremely buggy. It can hang forever if it doesn't find a network immediately.
139-
watchdogSetup();
140-
sleepSetup();
141-
pinsSetup();
142-
settingsSetup();
143-
wifiSetup();
223+
void setup() {
224+
serialSetup();
225+
226+
// We need a watchdog because the WifiMulti module is extremely buggy. It can hang forever if it doesn't find a network immediately.
227+
watchdogSetup();
228+
sleepSetup();
229+
pinsSetup();
230+
settingsSetup();
231+
wifiSetup();
232+
otapSetup();
144233
}
145234

146235
// Sensors used
147236
typedef struct {
148237
float airHumidity;
149238
float airTemperature;
150239
float soilMoisture;
151-
float light;
240+
//float light;
152241
} t_sensors;
153242

154243
t_sensors sensors = {};
155244

156245
// Converts a raw value into a percentage
157-
float toPercentage(int value, const int valueMin, const int valueMax, bool revert ) {
246+
float toPercentage(int value, int valueMin, int valueMax, bool revert, String type) {
247+
{
248+
char prefName[100];
249+
sprintf(prefName, "per.%s", type);
250+
preferences.begin(prefName, false);
251+
}
252+
valueMin = preferences.getInt("min", valueMin);
253+
valueMax = preferences.getInt("max", valueMax);
254+
if ( value > valueMax ) {
255+
valueMax = value;
256+
Serial.printf("Saving new max: %d\n", valueMax);
257+
preferences.putInt("max", valueMax);
258+
} else if ( value < valueMin ) {
259+
valueMin = value;
260+
Serial.printf("Saving new min: %d\n", valueMin);
261+
preferences.putInt("min", valueMin);
262+
}
263+
preferences.end();
264+
265+
Serial.printf("Value: %d, Min: %d, Max: %d\n", value, valueMin, valueMax);
266+
158267
float per = 100.0 * (value - valueMin) / (valueMax - valueMin);
159268
if ( revert ) {
160269
per = 100 - per;
161270
}
271+
162272
return per;
163273
}
164274

@@ -173,14 +283,20 @@ void sensorsFetchDht () {
173283
}
174284

175285
void sensorsFetchAdc () { // Fetching data from ADC
176-
int soilMoistureLevel = analogRead(PIN_SOIL);
177-
int lightLevel = analogRead(PIN_LIGHT);
178-
286+
const int nb_readings = 20;
287+
const int measures_time = 30000;
288+
int soilMoistureLevel = 0;
289+
290+
for( int i = 0 ; i < nb_readings; i++ ) {
291+
int reading = analogRead(PIN_SOIL);
292+
soilMoistureLevel += reading;
293+
Serial.printf("Soil moisture (raw): %d (%d)\n", reading, i);
294+
delay(measures_time/nb_readings);
295+
}
296+
soilMoistureLevel /= nb_readings;
179297
Serial.printf("Soil moisture (raw): %d\n", soilMoistureLevel);
180-
Serial.printf("Light (raw): %d\n", lightLevel);
181298

182-
sensors.soilMoisture = toPercentage(soilMoistureLevel, 1400, 3250, true);
183-
sensors.light = lightLevel; // Light gives completely bogus data
299+
sensors.soilMoisture = toPercentage(soilMoistureLevel, 1100, 3000, true, "soil");
184300

185301
Serial.printf("Soil moisture (corrected): %.2f%%\n", sensors.soilMoisture);
186302
}
@@ -191,21 +307,40 @@ void sensorsFetch() {
191307
}
192308

193309
void eedomusSend(int periphId, String value) {
194-
Serial.print(String("Eedomus: Setting \"")+ value+"\" for periphId "+ periphId + "... ");
195-
String url = String("http://api.eedomus.com/set?api_user=")+EEDOMUS_API_KEY+"&api_secret="+EEDOMUS_API_SECRET+"&action=periph.value&periph_id="+periphId+"&value="+value;
310+
if ( ! periphId ) {
311+
return;
312+
}
313+
Serial.print(String("Eedomus: Setting \"") + value + "\" for periphId " + periphId + "... ");
314+
String url = String("http://api.eedomus.com/set?api_user=") + EEDOMUS_API_KEY + "&api_secret=" + EEDOMUS_API_SECRET + "&action=periph.value&periph_id=" + periphId + "&value=" + value;
315+
316+
for ( int i = 0; i < 3; i++ ) {
317+
HTTPClient client;
318+
client.begin(url);
319+
int status = client.GET();
320+
Serial.printf("Done (%d)\n", status);
321+
if ( status == 200 ) {
322+
break;
323+
}
324+
}
325+
}
196326

197-
HTTPClient client;
198-
client.begin(url);
199-
int status = client.GET();
200-
Serial.printf("Done (%d)\n", status);
327+
void eedomusSend(int periphId, float value) {
328+
char format[100];
329+
sprintf(format, "%.2f", value);
330+
eedomusSend(periphId, format);
201331
}
202332

203333
void sendSensorsData() {
204334
sensorsFetch();
205-
eedomusSend(1486564, String(int(sensors.soilMoisture)));
206-
eedomusSend(1486569, String(int(sensors.light)));
207-
eedomusSend(1488106, String(int(sensors.airHumidity)));
208-
eedomusSend(1488113, String(int(sensors.airTemperature)));
335+
336+
if ( ! current_device_ids ) {
337+
Serial.print("No device set !\n");
338+
return;
339+
}
340+
341+
eedomusSend(current_device_ids->deviceIdSoil, sensors.soilMoisture);
342+
eedomusSend(current_device_ids->deviceIdAirHumidity, sensors.airHumidity);
343+
eedomusSend(current_device_ids->deviceIdAirTemperature, sensors.airTemperature);
209344
}
210345

211346
void loop() {

0 commit comments

Comments
 (0)