Introduction
Outdoor IoT solutions depend on a long lasting and stable power supply, but where to find in the wilderness? What immediately comes into mind is battery power, but due to the incompatability between battery voltages and supply voltage of IoT modules like Arduino or ESP8266, a voltage converter is usually part of the circuit, shamelessly draining even slightly oversized battery packs. Adding a solar panel into the mix is easy and comes quite cheap. I’m running an ESP8266 now for several months during winter and the Lipo battery voltage has now dropped to 3.8V, due to weeks of bad weather here in Munich. When the days get longer, the solar panel will certainly charge the battery up to its maximum level again.
Another reason for the prolonged battery performance certainly is, that the ESP8266 based Adafruit Huzzah module comes without USB controller chip, and that already saves a lot of power. On the other hand, it requires the developer to use a USB programming device like an FTDI Serial adapter. Another important factor of battery life is tHe ESP chip’s built in deep sleep logic which lets you put the module into sleep for years if necessary.
If someone is interested to read more about my solar powered weatherstation, please let me know. The sensor data is displayed on ThingSpeak.com.
Parts:
- Adafruit HUZZAH ESP8266 module
- BME280 temperature, humidity and air pressure sensor
- TP4056 Lithium battery power charger
- Decoupling Capacitor
- Resistor 1KOhm
- Power connector mini-JST for solar panel
- Power connector JST for battery pack
Now take a look at the code below. The loop() function is empty. SO why is that? Glad you asked. Because the Huzzah module will only wake up from deep sleep if an interrupt triggers the reset signal, the module will reset and everything starts from the beginning, which is the setup() function. So everything programmatic that needs to be done for this weatherstation must be done in the setup() function.
Pay some attention to the connectToMeteo() function. It reads the air pressure on sea level, provided on a web page from the LMU university in Munich. In fact, it filters the value from the web page which fortunately is not too complicated. The code below uses the value for computing the altitude at which the sensor is located. Since the sensor is never moved away from my balcony, the value should be quite stable, which would prove the calculations to be correct. Well, that’s the theory. Although the pressure value is subject to change frequently, the sensor library is given the air pressure on sea level when the pressure value is read from the sensor. This would be enough for the sensor to come up with a stable altitude value. Well, take a look at my Thingspeak channel and see for yourself.
#include <Wire.h> #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> Adafruit_BME280 bme; // I2C const char* ssid = "SSID"; const char* password = "password"; WiFiClient client; // sleep for this many seconds const int sleepSeconds = 300; // 5 minutes sleep // ThingSpeak Settings const int channelID = 325884; String writeAPIKey = "583BA12FI7WTS8PF"; // write API key for your ThingSpeak Channel const char* server = "api.thingspeak.com"; double v, a, h, t, p, dp; char temperatureFString[6]; char dpString[6]; char humidityString[6]; char pressureString[7]; char altitudeString[6]; char voltageString[6]; void setup() { // Serial.begin(9600); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); pinMode(A0, INPUT); Wire.begin(5, 4); delay(100); connect(); // Returns only if there is a connection delay(100); // ******** AIR PRESSURE AT SEA LEVEL ************** float nn = connectToMeteo(); if(bme.begin()) { delay(150); // Reading temperature or humidity takes about 250 milliseconds! // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) h = bme.readHumidity(); // Read temperature as Celsius (the default) t = bme.readTemperature(); // Read temperature as Fahrenheit (isFahrenheit = true) // float f = dht.readTemperature(true); p = bme.readPressure() / 100.0D; a = bme.readAltitude(nn); // Check if any reads failed and exit early (to try again). if (isnan(h) || isnan(t) || isnan(p)) { String s = "0"; s.toCharArray(temperatureFString, 6); s.toCharArray(humidityString, 6); s.toCharArray(pressureString, 7); s.toCharArray(altitudeString, 6); s.toCharArray(dpString, 6); } else { dtostrf(t, 5, 1, temperatureFString); dtostrf(h, 5, 1, humidityString); dtostrf(p, 5, 1, pressureString); dtostrf(a, 5, 1, altitudeString); // ******** DEW POINT ************** dp = computeDewPoint2(t, h); // dp = t-0.36*(100.0-h); Old dew point computation using Fahrenheit dtostrf(dp, 5, 1, dpString); // ******** VOLTAGE ************** double val = analogRead(A0); // Serial.println(val); v = val / 1023 * 6.3D; dtostrf(v, 5, 1, voltageString); if (client.connect(server, 80)) { String body = "field1="; body.concat(temperatureFString); body.concat("&field2="); body.concat(humidityString); body.concat("&field3="); body.concat(dpString); body.concat("&field4="); body.concat(voltageString); body.concat("&field5="); body.concat(pressureString); body.concat("&field6="); body.concat(altitudeString); // Serial.println(body); client.print("POST /update HTTP/1.1\n"); client.print("Host: api.thingspeak.com\n"); client.print("Connection: close\n"); client.print("X-THINGSPEAKAPIKEY: " + writeAPIKey + "\n"); client.print("Content-Type: application/x-www-form-urlencoded\n"); client.print("Content-Length: "); client.print(body.length()); client.print("\n\n"); client.print(body); client.print("\n\n"); client.stop(); delay(100); } } } // ESP.deepSleep(microseconds, mode) will put the chip into deep sleep. // Mode is one of: // WAKE_RF_DEFAULT // WAKE_RFCAL // WAKE_NO_RFCAL // WAKE_RF_DISABLED // GPIO16 needs to be tied to RST to wake from deepSleep. ESP.deepSleep(sleepSeconds * 1000000); delay(100); } double computeDewPoint2(double celsius, double humidity) { double RATIO = 373.15 / (273.15 + celsius); // RATIO was originally named A0, possibly confusing in Arduino context double SUM = -7.90298 * (RATIO - 1); SUM += 5.02808 * log10(RATIO); SUM += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ; SUM += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ; SUM += log10(1013.246); double VP = pow(10, SUM - 3) * humidity; double T = log(VP/0.61078); // temp var return (241.88 * T) / (17.558 - T); } void loop() { } void connect() { // config static IP if necessary // IPAddress ip(192, 168, 1, 106); // IPAddress gateway(192, 168, 1, 1); // IPAddress subnet(255, 255, 255, 0); // WiFi.config(ip, gateway, subnet); WiFi.setAutoReconnect(true); WiFi.begin(ssid, password); while(WiFi.status() != WL_CONNECTED) { delay(1000); // Serial.print("."); } // Serial.println(""); // Serial.print("IP address: "); // Serial.println(WiFi.localIP()); } float connectToMeteo() { String page = ""; WiFiClientSecure client; // Serial.printf("\n[Connecting to %s ... ", "www.meteo.physik.uni-muenchen.de"); if (client.connect("www.meteo.physik.uni-muenchen.de", 443)) { // Serial.println("connected]"); // Serial.println("[Sending a request]"); client.print(String("GET /mesomikro/stadt/messung.php") + " HTTP/1.1\r\n" + "Host: www.meteo.physik.uni-muenchen.de\r\n" + "Connection: close\r\n" + "\r\n" ); // Serial.println("[Response:]"); while (client.connected()) { if (client.available()) { String line = client.readStringUntil('\n'); page += line; // Serial.println(line); } } // Serial.println("\n[Disconnected]"); } else { // Serial.println("connection failed!]"); } client.stop(); int idx = page.indexOf(" hPa"); idx = page.indexOf(" hPa", idx + 4); String val = ""; int idx2 = page.indexOf(">", idx-8); val = page.substring(idx2+1, idx); return strToFloat(val); } float strToFloat(String str) { char carray[str.length() + 1]; // determine size of array str.toCharArray(carray, sizeof(carray)); // put str into an array return atof(carray); }