Sunday, May 26, 2019

Taking The IOT Weather Station Off the Grid

NodeMCU weather station hooked up to a solar charger circuit


A few weeks ago I put together a simple weather station using a NodeMCU based IOT device and a DHT22 temperature module.  I had the device sent its data to a cloud service where I would be able to see what the temperature and humidity were at my house from literally anywhere in the world.

While I was happy in how things turned out in order to power up the station I needed to have it connected to a USB power source.  Since I wanted to have the weather station to be located outside, I needed to find a way to keep it powered up without the need for it to be plugged into a wall socket.,

Since the NodeMCU only requires 5 volts to run, it does make it a perfect candidate to run off a battery. If it can be run off a battery, then that also means that the battery itself can be charged with a solar cell.

Parts needed to build the solar cell charger

With that in mind I went to my box of electronic LEGO and pulled out the following items:

With this collection of parts, I can easily put together a circuit that can charge a 3.7 Volt lithium battery via a solar cell. While the battery is being charged, the solar cell also provides power to the NodeMCU. When the sun is not out, the battery powers the NodeMCU via the 5 Volt converter module

As I mentioned in my Electronic LEGO post a couple of months back, this rather complex circuit can be put together by connecting just a few parts.

I started by attaching wires to the negative and positive terminals of the solar cell, followed by soldering the other ends of the wires to the input terminals of the solar charger controller.

Luckily the input connections were marked on the charger's circuit board.

Connecting the solar cell
Connecting the input to the charger controller

Once the solar cell was connected to the controller I then connected the battery holders to the battery terminals on the controller board.

While I suspect that one 3.7 volt battery probably would have sufficed, I had decided to wire three battery holders together in parallel in order to have as much storage capacity available for the NodeMCU, in case there are any extended periods of time where full sunlight is not available. Additionally, this would also add some redundancy to the system in case one of the batteries fail.

I had confirmed beforehand that the solar charger was quite happy charging 3 batteries. before I wired everything permanently together.

Wiring the battery holders in parallel
Wiring the batteries to the charger controller
Batteries hooked up to the controller

With the batteries and solar cell hooked up, the final step was to connect the 5-volt converter to the charge controller. Again this was done by attaching wires to the negative and positive terminals of the converter, followed by soldering the other ends of the wires to the load output terminals of the charger controller.

Wiring up the 5 Volt converter

With that, the solar power source for the NodeMCU was built.

To make sure that everything was working before I hooked up the NodeMCU, I first installed the batteries and placed the whole contraption out into the sunlight.

If everything was hooked up correctly, I should have seen a red status LED on the controller light up - which indicated that the batteries were charging. After a few hours, the LED should then change to green, which indicates that the batteries are fully charged.

Likewise, the 5-volt converter should also be showing a red LED indicating that it was working.

Luckily for me, everything lit up like it was supposed to on the first try!

Flushed with success, I then plugged in the NodeMCU and let it start logging the weather outside - secure in the knowledge that the device will be continuously powered thanks to the sun.

But, once the sun went down, the batteries only lasted about 6 hours, and I wasn't able to get them recharged enough the next day to keep the NodeMCU going.

Upon some additional research, I discovered that the NodeMCU is a bit of a power hog when it's running, even when it's not doing any actual work.

Apparently, this is a common issue with these devices. However, they do happen to have what is called a deep sleep mode which allows you to effectively power the NodeMCU down for a set period of time, have it wake up to do a task and then go back to sleep again.

This deep sleep mode is accomplished via a combination of programming code to invoke the sleep mode and a hardware configuration of connecting the Reset pin to the D0 pin on the NodeMCU.

The Reset pin is used to send a signal to the D0 pin to trigger the wake-up command.

This did pose a bit of a problem in that I had already allocated the D0 pin in my code to blink the LED that I was using to indicate that the system was working.  To solve that I was able to allocate the D4 pin as the blink pin for my LED,

I then stripped out the delay code that I had in my program and in its place, I implemented a command to put the NodeMCU into a deep sleep state every 30 mins.

So basically what the program will now do is:

  • Log onto the Losant site 
  • Send a burst of data to it for about 1 minute
  • Go to sleep for 30 minutes
  • Wake up and do it all over again.

Going back to my original program I changed it to this (changes that indicate the LED going to pin D0 and the invoking of the sleep more are highlighted in red):

/**
 * Example for sending temperature and humidity
 * to the cloud using the DHT22 and ESP8266
 *
 * Copyright (c) 2016 Losant IoT. All rights reserved.
 * https://www.losant.com
 */


#include "DHT.h"
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Losant.h>

#define DHTPIN 4     // what digital pin the DHT22 is connected to
#define DHTTYPE DHT22   // There are multiple kinds of DHT sensors

DHT dht(DHTPIN, DHTTYPE);

// WiFi credentials.
const char* WIFI_SSID = "<my WIFI ID>";
const char* WIFI_PASS = "<my password>";

// Losant credentials.
const char* LOSANT_DEVICE_ID = "<my device ID>";
const char* LOSANT_ACCESS_KEY = "<my access key>";
const char* LOSANT_ACCESS_SECRET = "<my access code>";


WiFiClientSecure wifiClient;

LosantDevice device(LOSANT_DEVICE_ID);

void connect() {

  // Connect to Wifi.
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(WIFI_SSID);

  // WiFi fix: https://github.com/esp8266/Arduino/issues/2186
  WiFi.persistent(false);
  WiFi.mode(WIFI_OFF);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  unsigned long wifiConnectStart = millis();

  while (WiFi.status() != WL_CONNECTED) {
    // Check to see if
    if (WiFi.status() == WL_CONNECT_FAILED) {
      Serial.println("Failed to connect to WIFI. Please verify credentials: ");
      Serial.println();
      Serial.print("SSID: ");
      Serial.println(WIFI_SSID);
      Serial.print("Password: ");
      Serial.println(WIFI_PASS);
      Serial.println();
    }

    delay(500);
    Serial.println("...");
    // Only try for 5 seconds.
    if(millis() - wifiConnectStart > 5000) {
      Serial.println("Failed to connect to WiFi");
      Serial.println("Please attempt to send updated configuration parameters.");
      return;
    }
  }

  Serial.println();
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  Serial.print("Authenticating Device...");
  HTTPClient http;
  http.begin("http://api.losant.com/auth/device");
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Accept", "application/json");

  /* Create JSON payload to sent to Losant
   *
   *   {
   *     "deviceId": "575ecf887ae143cd83dc4aa2",
   *     "key": "this_would_be_the_key",
   *     "secret": "this_would_be_the_secret"
   *   }
   *
   */

  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["deviceId"] = LOSANT_DEVICE_ID;
  root["key"] = LOSANT_ACCESS_KEY;
  root["secret"] = LOSANT_ACCESS_SECRET;
  String buffer;
  root.printTo(buffer);

  int httpCode = http.POST(buffer);

  if(httpCode > 0) {
      if(httpCode == HTTP_CODE_OK) {
          Serial.println("This device is authorized!");
      } else {
        Serial.println("Failed to authorize device to Losant.");
        if(httpCode == 400) {
          Serial.println("Validation error: The device ID, access key, or access secret is not in the proper format.");
        } else if(httpCode == 401) {
          Serial.println("Invalid credentials to Losant: Please double-check the device ID, access key, and access secret.");
        } else {
           Serial.println("Unknown response from API");
        }
      }
    } else {
        Serial.println("Failed to connect to Losant API.");

   }

  http.end();

  // Connect to Losant.
  Serial.println();
  Serial.print("Connecting to Losant...");

  device.connectSecure(wifiClient, LOSANT_ACCESS_KEY, LOSANT_ACCESS_SECRET);
  
  while(!device.connected()) {
    delay(500);
    Serial.print(".");
    Serial.println("Reboot");
    ESP.restart();
    
  }

  Serial.println("Connected!");
  Serial.println();
  Serial.println("This device is now ready for use!");
}

void setup() {
  Serial.begin(9600);
  Serial.setTimeout(2000);
  pinMode(D4, OUTPUT);

  // Wait for serial to initialize.
  while(!Serial) { }

  Serial.println("Device Started");
  Serial.println("-------------------------------------");
  Serial.println("Running DHT!");
  Serial.println("-------------------------------------");

  connect();
}

void report(double humidity, double tempC, double heatIndexC) 
{
  StaticJsonBuffer<400> jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["humidity"] = humidity;
  root["tempC"] = tempC;
  root["heatIndexC"] = heatIndexC;
  device.sendState(root);
  Serial.println("Reported!");
  delay(1000);
}

void callback() {
  Serial.flush();
}

int timeSinceLastRead = 0;
int timeTotal = 0;
void loop() {
   bool toReconnect = false;

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Disconnected from WiFi");
    toReconnect = true;
  }

  if (!device.connected()) {
    Serial.println("Disconnected from MQTT");
    Serial.println(device.mqttClient.state());
    toReconnect = true;
  }

  if (toReconnect) {
    connect();
  }

  device.loop();

  // Sleep for 30 mins.
  if (timeTotal > 1000) {
    Serial.println("Sleep");
    ESP.deepSleep(30*60*1000000);
    Serial.println("Awake");
  }
  // Report every 10 seconds.
  if(timeSinceLastRead > 10000) {
    digitalWrite(D4, HIGH);
    
    // Reading temperature or humidity takes about 250 milliseconds!
    // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
    float h = dht.readHumidity();
    // Read temperature as Celsius (the default)
    float t = dht.readTemperature();
    // Read temperature as Fahrenheit (isFahrenheit = true)
    float f = dht.readTemperature(true);

    // Check if any reads failed and exit early (to try again).
    if (isnan(h) || isnan(t) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");
      timeSinceLastRead = 0;
      return;
    }

    // Compute heat index in Fahrenheit (the default)
    float hif = dht.computeHeatIndex(f, h);
    // Compute heat index in Celsius (isFahreheit = false)
    float hic = dht.computeHeatIndex(t, h, false);

    Serial.print("Humidity: ");
    Serial.print(h);
    Serial.print(" %\t");
    Serial.print("Temperature: ");
    Serial.print(t);
    Serial.print(" *C ");
    Serial.print(f);
    Serial.print(" *F\t");
    Serial.print("Heat index: ");
    Serial.print(hic);
    Serial.print(" *C ");
    Serial.print(hif);
    Serial.println(" *F");
    report(h, t, hic);
      
    digitalWrite(D4, LOW);
    
    timeSinceLastRead = 0;
  }
  
  timeSinceLastRead += 100;
  timeTotal += 1;
  
  }

I also changed the LED connection from the D0 pin to the D4 pin on the NodeMCU. I then connected the Reset pin to the D0 pin.

In the end, the connections to the NodeMCU looked like this


Once I made the necessary wiring changes and uploaded the new code up to the NodeMCU, I plugged the NodeMCU back into the solar charger circuit.

This proved to be much more successful.

With the sleep mode implemented, the NodeMCU is only truly on for 2 minutes per hour, Which gives the solar charger ample time to charge the batteries during the sleep cycles and has minimized the power draw during the night so that the NodeMCU is easily able to operate for the entire night,

Now that I have the electronics of my weather station sorted out, I now need to figure out a suitable enclosure for my weather station so that it can survive the elements.

No comments:

Post a Comment