๐ต An indoor WiFi-connected temperature and humidity sensor ๐ถ
Wakup every 6 hours to read the temperature and humidity
Wakeup every 6 hours to read the temperature and humidity and send that value to IFTTT.
Serial output from the firmware.
#include <EEPROM.h> #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> #include <Ticker.h> #include <math.h> #include "Adafruit_Si7021.h" #include "./web.h" // Things to input // Wakeup every 10,800,000,000 ยตs = 10800e6 = 10,800s = 3 hours #define SLEEP_INTERVAL_DURATION 10800e6 #define SLEEP_DURATION_ENGLISH String("3 hours") #define MAX_SLEEP_COUNT 2 // 3 hours * 2 times = 6 hours // Options #define DEBUG true // Change to true if WiFi config need to be erased for a new SSID // Change back to false and flash the firmware immediately // TODO: Implement a factory reset button to erase all stored configurations bool willEraseWiFiCredentials = false; // Other constants #define EN 2 // GPIO02 on ESP-01 module, D4 on nodeMCU WeMos, or on-board LED #define LED 2 #define BATTERY_VOLT A0 #define USERBUTTON 12 // GPIO012 on ESP or D6 on WeMos #define CURRENT_SLEEP_COUNT EEPROM.read(CURRENT_SLEEP_INTERVAL_ADDR) #define IFTTT_KEY_LENGTH 22 #define CURRENT_SLEEP_INTERVAL_ADDR 30 // EEPROM add. to store sleep interval #define MAX_WIFI_RECONNECT_INTERVAL 20 // WiFi will try to connect for 20s #define MAX_AP_ON_MINUTES 5 // The AP mode will be on for 5 minutes // WiFi char ssid[50] = "secret"; char password[50] = "secret"; String AP_NamePrefix = "Cactus "; const char WiFiAPPSK[] = "hutscape"; const char* DomainName = "cactus"; // set domain name domain.local // LEDs and shift register int dataPin = 13; // pin D7 `GPIO13` on NodeMCU boards int clockPin = 14; // pin D5 `GPIO14` on NodeMCU boards int latchPin = 15; // pin D8 `GPIO15` on NodeMCU boards struct SensorValues { float temperature; float humidity; }; int userButtonValue = 1; bool isAPWebServerRunning = false; int apLoopCount = 0; Ticker ticker; SensorValues sensorValues; const char* host = "maker.ifttt.com"; const int httpsPort = 443; const int httpPort = 80; // Get fingerprint of maker.ifttt.com // echo | openssl s_client -connect maker.ifttt.com:443 |& openssl x509 -fingerprint -noout const char fingerprint[] PROGMEM = "AA:75:CB:41:2E:D5:F9:97:FF:5D:A0:8B:7D:AC:12:21:08:4B:00:8C"; ESP8266WebServer server(80); Adafruit_Si7021 sensor = Adafruit_Si7021(); void setup() { EEPROM.begin(512); Serial.begin(115200); disableWiFi(); debugPrintln(""); userButtonValue = digitalRead(USERBUTTON); debugPrintln("[INFO] Wakeup sleep count " + String(CURRENT_SLEEP_COUNT) + "/" + String(MAX_SLEEP_COUNT)); if (willEraseWiFiCredentials) { eraseWiFiCredentials(); debugPrintln("[INFO] Erasing WiFi credentials"); debugPrintln("[INFO] Read empty WiFi SSID: " + WiFi.SSID()); } if (!isCurrentSleepCountMax() && !hasUserPressedButton(userButtonValue)) { increaseSleepCount(); debugPrintln("[INFO] Going into deep sleep for " + SLEEP_DURATION_ENGLISH); goToSleep(); return; } initReadingBatteryVoltage(); initTempHumiditySensor(); resetSleepCount(); sensorValues = readTempHumidity(); if (hasUserPressedButton(userButtonValue)) { debugPrintln("[INFO] Wakeup on user button press!"); initShiftRegister(); displayHumidity(sensorValues.humidity); } enableWiFi(); bool isConnectedToWiFi = connectToWiFi(true); if (isConnectedToWiFi) { debugPrintln("[INFO] Connected succesfully to WiFi SSID " + WiFi.SSID()); debugPrintln("[INFO] WiFi connected! IP address: " + WiFi.localIP().toString()); } else { debugPrintln("[ERROR] Connection to WiFi failed"); debugPrintln("[INFO] Configuring access point"); initAccessPoint(); debugPrintln("[INFO] Started access point at IP " + WiFi.softAPIP().toString()); bool hasMDNSStarted = MDNS.begin(DomainName, WiFi.softAPIP()); if (!hasMDNSStarted) { debugPrintln("[ERROR] mDNS has failed to start"); } startServer(); debugPrintln("[INFO] WiFi is not configured!"); debugPrintln("[INFO] Connect to SSID 'Cactus NNNN'"); debugPrintln("[INFO] Go to http://cactus.local"); // Start blinking LED to indicate AP mode pinMode(LED_BUILTIN, OUTPUT); ticker.attach(1, blink); } } void loop() { if (isAPWebServerRunning) { MDNS.update(); server.handleClient(); if (++apLoopCount > MAX_AP_ON_MINUTES*600) { debugPrintln("[INFO] Going into deep sleep for " + SLEEP_DURATION_ENGLISH); goToSleep(); } delay(100); } else { ticker.detach(); sendToIFTTT(sensorValues, getBatteryVoltage()); debugPrintln(""); debugPrintln("[INFO] Going into deep sleep after task for " + SLEEP_DURATION_ENGLISH); goToSleep(); } } // Print functions void debugPrintln(String s) { if (DEBUG) { Serial.println(s); } } void debugPrint(String s) { if (DEBUG) { Serial.print(s); } } // Sleep functions bool isCurrentSleepCountMax() { return CURRENT_SLEEP_COUNT >= MAX_SLEEP_COUNT; } void increaseSleepCount() { EEPROM.write(CURRENT_SLEEP_INTERVAL_ADDR, CURRENT_SLEEP_COUNT + 1); EEPROM.commit(); } void resetSleepCount() { EEPROM.write(CURRENT_SLEEP_INTERVAL_ADDR, 1); EEPROM.commit(); } // EEPROM String readKey() { String readStr; char readChar; for (int i = 0; i < IFTTT_KEY_LENGTH; ++i) { readChar = char(EEPROM.read(i)); readStr += readChar; } return readStr; } String readEvent() { String word; char readChar; // IFTTT Event name is stored after the IFTTT Key in EEPROM int i = IFTTT_KEY_LENGTH; while (readChar != '\0') { readChar = char(EEPROM.read(i)); delay(10); i++; if (readChar != '\0') { word += readChar; } } word[word.length() - 1] = '\0'; return word; } void clearEEPROM() { EEPROM.begin(512); for (int i = 0; i < 512; i++) { EEPROM.write(i, 0); } EEPROM.commit(); } void writeKey(String writeStr) { delay(10); for (int i = 0; i < writeStr.length(); ++i) { EEPROM.write(i, writeStr[i]); } EEPROM.commit(); } void writeEvent(String eventName) { delay(10); int address = 0; int eventNameIndex = 0; int initialAddress = IFTTT_KEY_LENGTH; int finalAddress = IFTTT_KEY_LENGTH + eventName.length(); for (address = initialAddress; address < finalAddress; ++address) { delay(10); EEPROM.write(address, eventName[eventNameIndex]); eventNameIndex++; } EEPROM.write(finalAddress, '\0'); EEPROM.commit(); } // Battery float getBatteryVoltage() { unsigned int raw = 0; float volt = 0.0; float batteryVoltage = 0.0; // NOTE: Get 10 analog values and smoothen it for (int count = 0; count < 10; count++) { raw = analogRead(A0); volt = raw / 1023.0; volt *= 4.2; batteryVoltage += volt; } batteryVoltage /= 10; Serial.print("[INFO] Battery voltage is "); Serial.print(batteryVoltage); Serial.println("V"); return volt; } // Sensor functions bool hasUserPressedButton(int userButtonValue) { return userButtonValue == 0; } void blink() { int state = digitalRead(LED_BUILTIN); digitalWrite(LED_BUILTIN, !state); } void ledOFF() { digitalWrite(LED_BUILTIN, HIGH); } void initShiftRegister() { pinMode(latchPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); pinMode(EN, OUTPUT); digitalWrite(EN, LOW); } void initReadingBatteryVoltage() { pinMode(BATTERY_VOLT, INPUT); } void initTempHumiditySensor() { if (!sensor.begin()) { Serial.println("[ERROR] Did not find Si7021 sensor!"); while (true) {} } } SensorValues readTempHumidity(void) { SensorValues sensorValues = { 0.0, 0.0 }; // NOTE: Get 10 sensor values and smoothen it for (int count = 0; count < 10; count++) { sensorValues.temperature += sensor.readTemperature(); sensorValues.humidity += sensor.readHumidity(); } sensorValues.temperature /= 10; sensorValues.humidity /= 10; Serial.print("[INFO] Temperature: "); Serial.print(sensorValues.temperature); Serial.print(" C, Humidity: "); Serial.print(sensorValues.humidity); Serial.println(" RH%"); return sensorValues; } void displayHumidity(float humidity) { int barHumidity = humidity/20; String sBar = "[INFO] Display Humidity in LED: " + String(barHumidity) + " LEDs"; Serial.println(sBar); displayLED(pow(2, barHumidity) -1); delay(10000); // display the humidity LEDs on-board for 10 seconds } void displayLED(int lednumber) { digitalWrite(latchPin, LOW); shiftOut(dataPin, clockPin, MSBFIRST, lednumber); digitalWrite(latchPin, HIGH); } // Sleep and Wakup functions void goToSleep() { ESP.deepSleep(SLEEP_INTERVAL_DURATION, WAKE_RF_DISABLED); } // WiFi functions void eraseWiFiCredentials() { WiFi.disconnect(true); ESP.eraseConfig(); willEraseWiFiCredentials = false; } void enableWiFi() { WiFi.forceSleepWake(); // wakeup WiFi modem delay(1); } void disableWiFi() { WiFi.mode(WIFI_OFF); WiFi.forceSleepBegin(); delay(1); } bool connectToWiFi(bool useCredsFromFlash) { if (useCredsFromFlash) { WiFi.begin(); } else { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); } int count = 0; while (WiFi.status() != WL_CONNECTED) { delay(500); if (++count % 10 == 0) { debugPrintln("."); } else { debugPrint("."); } if (count >= MAX_WIFI_RECONNECT_INTERVAL) { if (count % 10 != 0) { debugPrintln(""); } return false; } } if (count % 10 != 0) { debugPrintln(""); } WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); return true; } void initAccessPoint() { WiFi.mode(WIFI_AP); WiFi.softAP(createAPName().c_str(), WiFiAPPSK); } String createAPName() { uint8_t mac[WL_MAC_ADDR_LENGTH]; WiFi.softAPmacAddress(mac); String macID = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) + String(mac[WL_MAC_ADDR_LENGTH - 1], HEX); macID.toUpperCase(); return AP_NamePrefix + macID; } void startServer() { server.on("/", handleRoot); const char * headerkeys[] = {"User-Agent", "Cookie"}; size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*); server.collectHeaders(headerkeys, headerkeyssize); server.begin(); isAPWebServerRunning = true; } void handleRoot() { if (server.hasArg("ssid") && server.hasArg("password") && server.hasArg("key") && server.hasArg("event")) { String receivedSSID = server.arg("ssid"); debugPrintln("[INFO] WiFi SSID received: " + receivedSSID); receivedSSID.toCharArray(ssid, 50); debugPrintln("[INFO] WiFi password received"); server.arg("password").toCharArray(password, 50); // Serial.println(server.arg("password")); clearEEPROM(); debugPrintln("[INFO] IFTTT key received!"); writeKey(server.arg("key")); // Serial.println(server.arg("key")); debugPrintln("[INFO] IFTTT event name received!"); writeEvent(server.arg("event")); // Serial.println(server.arg("event")); returnSuccessPage(); delay(1000); bool hasConectedToWifi = connectToWiFi(false); if (!hasConectedToWifi) { Serial.println("[ERROR] Cannot connect to WiFi after AP mode!"); server.sendHeader("Location", "/"); server.sendHeader("Cache-Control", "no-cache"); server.sendHeader("Set-Cookie", "ESPSESSIONID=1"); server.send(301); return; } debugPrintln("[INFO] Connected to WiFi after AP mode!"); isAPWebServerRunning = false; return; } returnConfigPage(); } void returnConfigPage() { server.send(200, "text/html", content); } void returnSuccessPage() { String content = "<html><body><h1>Received!</h1></body></html>"; server.send(301, "text/html", content); } // Cloud void sendToIFTTT(SensorValues sensorValues, float batteryVoltage) { Serial.println("[INFO] Sending IFTTT notification..."); WiFiClientSecure client; client.setFingerprint(fingerprint); // Serial.println("[INFO] Setting fingerprint..."); if (!client.connect(host, httpsPort)) { Serial.println("[ERROR] Connection failed"); return; } Serial.println("[INFO] Client connected"); String url = "/trigger/" + readEvent() + "/with/key/" + readKey(); // Serial.print("URL: "); // Serial.println(url); char data[34]; // TODO: Never use sprintf. Use snprintf instead. [runtime/printf] sprintf(data, "value1=%03d&value2=%03d&value3=%2.1f", roundToInt(sensorValues.temperature), roundToInt(sensorValues.humidity), batteryVoltage); Serial.print("[INFO] Data sent: "); Serial.println(data); Serial.print("[INFO] Data size: "); Serial.println(sizeof(data)); client.println(String("POST ") + url + " HTTP/1.1"); client.println(String("Host: ") + host); client.println(String("Content-Type: application/x-www-form-urlencoded")); client.print(String("Content-Length: ")); client.println(sizeof(data)); client.println(); client.println(data); Serial.println("[INFO] Client posted"); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 20000) { Serial.println("[ERROR] Client Timeout!"); client.stop(); return; } } Serial.println("[INFO] Reply from client:"); while (client.available()) { String line = client.readStringUntil('\r'); Serial.print(line); } client.stop(); return; } // Others int roundToInt(float value) { return (int)round(value); }
BOARD?=esp8266com:esp8266:d1_mini PORT?=/dev/cu.wchusbserial1410 .PHONY: default lint all flash clean default: lint all flash clean lint: cpplint --extensions=ino --filter=-legal/copyright,-readability/todo,-readability/casting,-runtime/int,-whitespace/line_length,-runtime/printf *.ino all: arduino-cli compile --fqbn $(BOARD) ./ flash: arduino-cli upload -p $(PORT) --fqbn $(BOARD) ./ clean: rm -f .*.bin rm -f .*.elf