├── HiGrowEsp16e ├── README.md ├── tools.ino ├── credentials.ino ├── HiGrowEsp16e.ino └── handleHttp.ino ├── _config.yml ├── README.md ├── LICENSE └── HiGrowEsp32 └── HiGrowEsp32.ino /HiGrowEsp16e/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HiGrow 2 | ======== 3 | 4 | * HiGrow is an OpenSource sensor with wifi to cloud capabilities for deferred monitoring plants. Please visit https://higrow.tech to get more information and instructions on how to create your own. -------------------------------------------------------------------------------- /HiGrowEsp16e/tools.ino: -------------------------------------------------------------------------------- 1 | /** Is this an IP? */ 2 | boolean isIp(String str) { 3 | for (int i = 0; i < str.length(); i++) { 4 | int c = str.charAt(i); 5 | if (c != '.' && (c < '0' || c > '9')) { 6 | return false; 7 | } 8 | } 9 | return true; 10 | } 11 | 12 | /** IP to String? */ 13 | String toStringIp(IPAddress ip) { 14 | String res = ""; 15 | for (int i = 0; i < 3; i++) { 16 | res += String((ip >> (8 * i)) & 0xFF) + "."; 17 | } 18 | res += String(((ip >> 8 * 3)) & 0xFF); 19 | return res; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /HiGrowEsp16e/credentials.ino: -------------------------------------------------------------------------------- 1 | /** Load WLAN credentials from EEPROM */ 2 | void loadCredentials() { 3 | EEPROM.begin(512); 4 | EEPROM.get(0, ssid); 5 | EEPROM.get(0+sizeof(ssid), password); 6 | EEPROM.get(0+sizeof(ssid)+sizeof(password), deviceid); 7 | char ok[2+1]; 8 | EEPROM.get(0+sizeof(ssid)+sizeof(password)+sizeof(deviceid), ok); 9 | EEPROM.end(); 10 | Serial.println(ok); 11 | if (String(ok) != String("OK")) { 12 | ssid[0] = 0; 13 | password[0] = 0; 14 | deviceid[0] = 0; 15 | } 16 | } 17 | 18 | /** Store WLAN credentials to EEPROM */ 19 | void saveCredentials() { 20 | EEPROM.begin(512); 21 | EEPROM.put(0, ssid); 22 | EEPROM.put(0+sizeof(ssid), password); 23 | EEPROM.put(0+sizeof(ssid)+sizeof(password), deviceid); 24 | char ok[2+1] = "OK"; 25 | EEPROM.put(0+sizeof(ssid)+sizeof(password)+sizeof(deviceid), ok); 26 | EEPROM.commit(); 27 | EEPROM.end(); 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /HiGrowEsp32/HiGrowEsp32.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "DHT.h" 4 | #include 5 | 6 | //#define DHTTYPE DHT11 // DHT 11 7 | //#define DHTTYPE DHT21 // DHT 21 (AM2301) 8 | #define DHTTYPE DHT11 // DHT 22 (AM2302), AM2321 9 | #define uS_TO_S_FACTOR 1000000LL 10 | 11 | unsigned long now; 12 | int DEEPSLEEP_SECONDS = 1800; 13 | 14 | WiFiServer server(80); 15 | 16 | HTTPClient http; 17 | 18 | uint64_t chipid; 19 | 20 | long timeout; 21 | 22 | const int dhtpin = 22; 23 | const int soilpin = 32; 24 | const int POWER_PIN = 34; 25 | const int LIGHT_PIN = 33; 26 | 27 | // Initialize DHT sensor. 28 | DHT dht(dhtpin, DHTTYPE); 29 | 30 | // Temporary variables 31 | static char celsiusTemp[7]; 32 | static char humidityTemp[7]; 33 | 34 | // Client variables 35 | char linebuf[80]; 36 | int charcount=0; 37 | 38 | String ssid = "YOURSSID"; 39 | String pwd = "YOURPWD"; 40 | 41 | char deviceid[21]; 42 | 43 | void setup() { 44 | dht.begin(); 45 | 46 | Serial.begin(115200); 47 | while(!Serial) { 48 | ; // wait for serial port to connect. Needed for native USB port only 49 | } 50 | esp_deep_sleep_enable_timer_wakeup(1800 * uS_TO_S_FACTOR); 51 | esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); 52 | 53 | pinMode(16, OUTPUT); 54 | pinMode(POWER_PIN, INPUT); 55 | digitalWrite(16, LOW); 56 | 57 | timeout = 0; 58 | 59 | chipid = ESP.getEfuseMac(); 60 | sprintf(deviceid, "%" PRIu64, chipid); 61 | Serial.print("DeviceId: "); 62 | Serial.println(deviceid); 63 | } 64 | 65 | void loop() { 66 | 67 | char body[1024]; 68 | digitalWrite(16, LOW); //switched on 69 | 70 | sensorsData(body); 71 | http.begin("http://api.higrow.tech/api/records"); 72 | http.addHeader("Content-Type", "application/json"); 73 | int httpResponseCode = http.POST(body); 74 | Serial.println(httpResponseCode); 75 | esp_sleep_enable_timer_wakeup(DEEPSLEEP_SECONDS * uS_TO_S_FACTOR); 76 | esp_deep_sleep_start(); 77 | 78 | } 79 | 80 | void sensorsData(char* body){ 81 | 82 | //This section read sensors 83 | timeout = millis(); 84 | 85 | int waterlevel = analogRead(soilpin); 86 | int lightlevel = analogRead(LIGHT_PIN); 87 | 88 | waterlevel = map(waterlevel, 0, 4095, 0, 1023); 89 | waterlevel = constrain(waterlevel, 0, 1023); 90 | lightlevel = map(lightlevel, 0, 4095, 0, 1023); 91 | lightlevel = constrain(lightlevel, 0, 1023); 92 | 93 | // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) 94 | float humidity = dht.readHumidity(); 95 | // Read temperature as Celsius (the default) 96 | float temperature = dht.readTemperature(); 97 | 98 | float hic = dht.computeHeatIndex(temperature, humidity, false); 99 | dtostrf(hic, 6, 2, celsiusTemp); 100 | dtostrf(humidity, 6, 2, humidityTemp); 101 | 102 | String did = String(deviceid); 103 | String water = String((int)waterlevel); 104 | String light = String((int)lightlevel); 105 | 106 | strcpy(body, "{\"deviceId\":\""); 107 | strcat(body, did.c_str()); 108 | strcat(body, "\",\"water\":\""); 109 | strcat(body, water.c_str()); 110 | strcat(body, "\",\"light\":\""); 111 | strcat(body, light.c_str()); 112 | strcat(body, "\",\"humidity\":\""); 113 | strcat(body, humidityTemp); 114 | strcat(body, "\",\"temperature\":\""); 115 | strcat(body, celsiusTemp); 116 | strcat(body, "\"}"); 117 | 118 | if(lightlevel<100){ 119 | DEEPSLEEP_SECONDS = 10800; 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /HiGrowEsp16e/HiGrowEsp16e.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | //#include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | const int SENSOR_SLEEP = 300000; 11 | 12 | const size_t MAX_CONTENT_SIZE = 4096; 13 | const unsigned long HTTP_TIMEOUT = 10000; // max respone time from server 14 | 15 | const char *softAP_ssid = "HiGrowSetupWiFi"; 16 | //const char *softAP_password = "HiGrowSetupWiFi"; 17 | 18 | const char *myHostname = "higrow.tech"; 19 | const char* host = "higrowapp.azurewebsites.net"; 20 | 21 | char ssid[32] = ""; 22 | char password[32] = ""; 23 | char deviceid[48] = ""; 24 | 25 | const byte DNS_PORT = 53; 26 | DNSServer dnsServer; 27 | 28 | ESP8266WebServer server(80); 29 | SimpleDHT11 dht11; 30 | 31 | IPAddress apIP(192, 168, 4, 1); 32 | IPAddress netMsk(255, 255, 255, 0); 33 | 34 | boolean connect; 35 | boolean sleep; 36 | boolean setupMode; 37 | 38 | long setupStartTime = 0; 39 | 40 | int status = WL_IDLE_STATUS; 41 | int waitTime = 1000; 42 | 43 | int thGrow = D4; 44 | int anaGrow = A0; 45 | 46 | void setup() { 47 | 48 | initiate(); 49 | setApMode(); 50 | } 51 | 52 | void loop() { 53 | if(setupMode){ 54 | apMode(); 55 | }else{ 56 | sensorMode(); 57 | } 58 | } 59 | 60 | void initiate(){ 61 | pinMode(thGrow,INPUT); 62 | setupStartTime = millis(); 63 | 64 | Serial.begin(9600); 65 | 66 | loadCredentials(); // Load WLAN credentials from network 67 | delay(100); 68 | connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID 69 | sleep=false; 70 | setupMode=true; 71 | } 72 | 73 | void connectWifi() { 74 | WiFi.disconnect(); 75 | WiFi.begin ( ssid, password ); 76 | int connRes = WiFi.waitForConnectResult(); 77 | } 78 | 79 | void setApMode(){ 80 | Serial.println("Setup AP"); 81 | waitTime = 50; 82 | /* You can remove the password parameter if you want the AP to be open. */ 83 | WiFi.softAPConfig(apIP, apIP, netMsk); 84 | WiFi.softAP(softAP_ssid); 85 | delay(500); // Without delay I've seen the IP address blank 86 | 87 | /* Setup the DNS server redirecting all the domains to the apIP */ 88 | //dnsServer.setErrorReplyCode(DNSReplyCode::NoError); 89 | dnsServer.start(DNS_PORT, "*", apIP); 90 | 91 | /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */ 92 | server.on("/", handleRoot); 93 | server.on("/sensor", handleSensor); 94 | server.on("/wifi", handleWifi); 95 | server.on("/wifisave", handleWifiSave); 96 | server.on("/start", handleStart); 97 | //server.on("/generate_204", handleRoot); //Android captive portal. Maybe not needed. Might be handled by notFound handler. 98 | //server.on("/fwlink", handleRoot); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. 99 | //server.onNotFound ( handleNotFound ); 100 | server.begin(); // Web server start 101 | delay(waitTime); 102 | } 103 | 104 | void setWifiMode(){ 105 | Serial.println("Setup WiFi"); 106 | waitTime = 100; 107 | WiFi.mode(WIFI_STA); 108 | delay(waitTime); 109 | } 110 | 111 | void apMode(){ 112 | dnsServer.processNextRequest(); 113 | server.handleClient(); 114 | if((setupStartTime + 600000) < millis()){ 115 | Serial.println("setup timeout reached"); 116 | setupMode=false; 117 | } 118 | if(!setupMode){ 119 | Serial.println("setup is over setting sensor mode up"); 120 | setWifiMode(); 121 | } 122 | } 123 | void sensorMode(){ 124 | if(sleep){ 125 | WiFi.forceSleepWake(); 126 | delay(100); 127 | sleep=false; 128 | } 129 | waitTime = 500; 130 | if (connect) { 131 | Serial.println("connect wifi"); 132 | connect = false; 133 | connectWifi(); 134 | } 135 | int s = WiFi.status(); 136 | if (status != s) { 137 | Serial.println("WiFi status changed"); 138 | status = s; 139 | } 140 | if(status == WL_CONNECTED && strlen(deviceid) > 0){ 141 | Serial.println("WiFi connected"); 142 | postRecord(); 143 | waitTime = SENSOR_SLEEP; 144 | sleep=true; 145 | 146 | WiFi.mode(WIFI_OFF); 147 | WiFi.forceSleepBegin(); 148 | delay(100); 149 | } else if (status == WL_NO_SSID_AVAIL) { 150 | Serial.println("wifi no ssid avail"); 151 | WiFi.disconnect(); 152 | delay(500); 153 | setWifiMode(); 154 | delay(500); 155 | connect = true; 156 | } else if (status == WL_NO_SHIELD) { 157 | Serial.println("wifi no shield"); 158 | } else if (status == WL_SCAN_COMPLETED) { 159 | Serial.println("wifi scan completed"); 160 | } else if (status == WL_CONNECT_FAILED) { 161 | Serial.println("wifi failed"); 162 | setWifiMode(); 163 | } else if (status == WL_CONNECTION_LOST) { 164 | Serial.println("wifi lost"); 165 | } else if (status == WL_DISCONNECTED) { 166 | Serial.println("wifi is disconnected"); 167 | connect = true; 168 | } else if (status == WL_IDLE_STATUS) { 169 | Serial.println("wifi is idle"); 170 | } else { 171 | Serial.println(status); 172 | setWifiMode(); 173 | } 174 | delay(waitTime); 175 | } 176 | 177 | void postRecord(){ 178 | char body[1024]; 179 | String response; 180 | 181 | byte temperature = 0; 182 | byte humidity = 0; 183 | byte data[40] = {0}; 184 | if (dht11.read(thGrow, &temperature, &humidity, data)) { 185 | Serial.print("Read DHT11 failed"); 186 | } 187 | 188 | String water = String(analogRead(anaGrow)); 189 | char warray[4]; 190 | water.toCharArray(warray,4); 191 | 192 | String temp = String((double)temperature); 193 | char tarray[4]; 194 | temp.toCharArray(tarray,4); 195 | 196 | String humi = String((int)humidity); 197 | char harray[4]; 198 | humi.toCharArray(harray,4); 199 | 200 | strcpy(body,"{\"deviceId\":\""); 201 | strcat(body,deviceid); 202 | strcat(body,"\",\"water\":\""); 203 | strcat(body,warray); 204 | strcat(body,"\",\"humidity\":\""); 205 | strcat(body,harray); 206 | strcat(body,"\",\"temperature\":\""); 207 | strcat(body,tarray); 208 | strcat(body,"\"}"); 209 | Serial.println(body); 210 | RestClient client = RestClient(host); 211 | client.setContentType("application/json"); 212 | int code = client.post("/api/records",body,&response); 213 | Serial.println(code); 214 | Serial.println(response); 215 | } 216 | 217 | -------------------------------------------------------------------------------- /HiGrowEsp16e/handleHttp.ino: -------------------------------------------------------------------------------- 1 | /** Handle root or redirect to captive portal */ 2 | void handleRoot() { 3 | // if (captivePortal()) { // If captive portal redirect instead of displaying the page. 4 | // return; 5 | // } 6 | sendHtmlHeader(); 7 | server.sendContent( 8 | "

Modalità impostazione HiGrow

" 9 | ); 10 | server.sendContent( 11 | "
" 12 | "

Impostazioni attuali:
" 13 | ); 14 | server.sendContent(String("SSID: ") + ssid + "
"); 15 | server.sendContent(String("Password: ") + password + "
"); 16 | server.sendContent(String("Device Id: ") + deviceid + "

"); 17 | 18 | server.sendContent( 19 | "
" 20 | ); 21 | server.sendContent( 22 | "" 23 | ); 24 | server.sendContent( 25 | "" 26 | ); 27 | sendHtmlFooter(); 28 | server.client().stop(); // Stop is needed because we sent no content length 29 | } 30 | 31 | /** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ 32 | //boolean captivePortal() { 33 | // if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname)+".local")) { 34 | // Serial.println("Request redirected to captive portal"); 35 | // server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true); 36 | // server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. 37 | // server.client().stop(); // Stop is needed because we sent no content length 38 | // return true; 39 | // } 40 | // return false; 41 | //} 42 | 43 | /** Wifi config page handler */ 44 | void handleWifi() { 45 | sendHtmlHeader(); 46 | server.sendContent( 47 | "

Connetti HiGrow alla rete WiFi

" 48 | ); 49 | Serial.println("scan start"); 50 | int n = WiFi.scanNetworks(); 51 | Serial.println("scan done"); 52 | if (n > 0) { 53 | server.sendContent( 54 | "\r\n
" 55 | ); 56 | server.sendContent( 57 | "" 64 | "
" 65 | "
" 66 | "
" 67 | ); 68 | }else{ 69 | server.sendContent( 70 | "

Nessuna WiFi trovata :(

" 71 | ); 72 | } 73 | server.sendContent( 74 | "

Torna alla home

" 75 | ); 76 | sendHtmlFooter(); 77 | server.client().stop(); // Stop is needed because we sent no content length 78 | } 79 | 80 | /** Handle the WLAN save form and redirect to WLAN config page again */ 81 | void handleWifiSave() { 82 | Serial.println("wifi save"); 83 | server.arg("n").toCharArray(ssid, sizeof(ssid) - 1); 84 | server.arg("p").toCharArray(password, sizeof(password) - 1); 85 | server.arg("d").toCharArray(deviceid, sizeof(deviceid) - 1); 86 | server.sendHeader("Location", "sensor", true); 87 | server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 88 | server.sendHeader("Pragma", "no-cache"); 89 | server.sendHeader("Expires", "-1"); 90 | server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. 91 | server.client().stop(); // Stop is needed because we sent no content length 92 | saveCredentials(); 93 | connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID 94 | } 95 | 96 | void handleNotFound() { 97 | // if (captivePortal()) { // If caprive portal redirect instead of displaying the error page. 98 | // return; 99 | // } 100 | String message = "File Not Found\n\n"; 101 | message += "URI: "; 102 | message += server.uri(); 103 | message += "\nMethod: "; 104 | message += ( server.method() == HTTP_GET ) ? "GET" : "POST"; 105 | message += "\nArguments: "; 106 | message += server.args(); 107 | message += "\n"; 108 | 109 | for ( uint8_t i = 0; i < server.args(); i++ ) { 110 | message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n"; 111 | } 112 | server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 113 | server.sendHeader("Pragma", "no-cache"); 114 | server.sendHeader("Expires", "-1"); 115 | server.send ( 404, "text/plain", message ); 116 | } 117 | 118 | void handleSensor(){ 119 | sendHtmlHeader(); 120 | server.sendContent( 121 | "

Dati sensori

" 122 | ); 123 | double anavalue = ((double)analogRead(anaGrow)/1023)*100; 124 | byte temperature = 0; 125 | byte humidity = 0; 126 | byte data[40] = {0}; 127 | if (dht11.read(thGrow, &temperature, &humidity, data)) { 128 | Serial.print("Read DHT11 failed"); 129 | } 130 | server.sendContent( 131 | "

il livello di idratazione del terreno è: " 132 | ); 133 | int level = ((anavalue)>100)?100:(int)(anavalue); 134 | server.sendContent(String(level)); 135 | server.sendContent( 136 | "%

" 137 | ); 138 | server.sendContent( 139 | "

la temperatura dell\'ambiente è: " 140 | ); 141 | server.sendContent(String((double)temperature)); 142 | server.sendContent( 143 | "C°

" 144 | ); 145 | server.sendContent( 146 | "

il livello di umidita dell\'ambiente è: " 147 | ); 148 | server.sendContent(String(humidity)); 149 | server.sendContent( 150 | "%

" 151 | ); 152 | server.sendContent( 153 | "" 154 | "" 155 | ); 156 | sendHtmlFooter(); 157 | server.client().stop(); 158 | } 159 | 160 | void handleStart(){ 161 | setupMode=false; 162 | sendHtmlHeader(); 163 | server.sendContent( 164 | "

Ora HiGrow è in modalità sensore. Inseriscilo nel terreno.

" 165 | ); 166 | server.sendContent( 167 | "

Per riavviare la modalità setup riavviare il tuo device

" 168 | ); 169 | sendHtmlFooter(); 170 | server.client().stop(); 171 | } 172 | 173 | void sendHtmlHeader(){ 174 | server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 175 | server.sendHeader("Pragma", "no-cache"); 176 | server.sendHeader("Expires", "-1"); 177 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 178 | server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. 179 | server.sendContent( 180 | "" 181 | "" 182 | "HiGrow iot sensor by Zepfiro v0.1" 183 | "" 189 | "" 190 | "" 191 | "" 194 | "
" 195 | ); 196 | } 197 | 198 | void sendHtmlFooter(){ 199 | server.sendContent( 200 | "
" 201 | ); 202 | } 203 | 204 | --------------------------------------------------------------------------------