Introduction: Iot Weather Station - Part 2

About: Software Engineer with more than 20 years of experience

This is the last part of this tutorial. If you are following, you remember that in the Part 1, we put together the skeleton for an initial Iot solution for our weather station.

In the part 1 we decided the code and the materials necessary to read data from a sensor using a FSEESP-85V13 or any ESP8266 family device.

Now we will finish our project with the following:

  • We will create a device to read Temp/Hum from its own peripheral and send the data to an Iot provider with deep sleep capabilities. We will call this device the Transmitter
  • Change our receiver to read that data published and display in the OLED along with the data from its own peripheral.

Lets go to the materials:

Supplies

  • A DHT11 Module. Keep in mind these modules has electrical components too close to the sensor. So, a deep sleep is a MUST have to keep the variation low. I tried some of these modules from different brands and found this one to be the best as long as you use it in a vertical position and use deep sleep

Additional tools

  • USB to UART programmer. Give preference to one that has the auto upload circuitry. As we are writing code as it goes, it is nice to have something that you wont need to press any button in order to have your code uploaded. The suggested below also expose the connections to connect your peripherals without having to plug/unplug all the time. Really convenient

Step 1: Wiring Up.

The wiring for this part is the same as the Part 1 for the prototyping phase. Except that will not be using the OLED for the transmitter device once its goal is to send the data to the an Iot Provider.

But, the configuration has everything we need for coding and uploading to the device. So, lets keep it simple.

Step 2: Requirements - Transmitter

Same as the Part 1, I am using platformio for coding. And the reason is the dependencies management for the code. It is all set in platformio.ini file and the requirements for the transmitter is as below:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:esp8285]
platform = espressif8266
board = esp8285
framework = arduino
lib_ldf_mode = chain+
upload_resetmethod = nodemcu
board_build.f_cpu = 26000000L

lib_deps = 
	olikraus/U8g2 @ ^2.28.8
	arduino-libraries/NTPClient @ ^3.1.0
	fullstackelectron/FSEDTH11 @ ^1.0.0
	fullstackelectron/FSESPIFFS @ ^1.0.0
	fullstackelectron/FSEWifiManager @ ^1.0.0
	fullstackelectron/FSEThingSpeak @ ^1.0.0
	
upload_speed = 460800

Step 3: The Code - Transmitter

All dependencies in place, it makes the code to push data to Iot a breeze. I just want to make a small note about the Deep Sleep capability of FSEESP-85V13 module. If you already applied the solder to enable the deep sleep, you may face troubles uploading your code.

This is because the Deep Sleep enable feature ties together GPIO16 and RST of ESP8285 chip. And that will cause the programmer to have a hard time to reset the module to put it in a flash mode. While the programmer is trying to reset the chip, there is another PIN trying to keep it alive.

To overcome that, you may have to add a way to reset the module manually. this is rather simple though. Just leave a loose wire connected to RST in the module and when the programmer is not able to reset the module, just manually connect that wire to GND. That should do the trick. Another option is just add a Reset button to your wiring. As you can see in one of the images for this step.

#include <Arduino.h>
#include <FSEWifiManager.h>
#include <FSEDHT.h>
#include "FSEThingSpeak.h"

#define deep_sleep

FSEDHT dht(2);
FSEWifiManager myWifiManager;
FSEThingSpeak iot;

#define IOT_URL_PARAM_NAME "iot_thingspeak_url"
#define IOT_API_KEY_PARAM_NAME "iot_api_Key"
#define DEEP_SLEEP_TIME_PARAM_NAME "dessp_sleep_minutes"

volatile float humidity=0.0, temperature=0.0;

void sendData(FSEDHT *dht);

unsigned long sleepMilliseconds;
void setup() {
	Serial.begin(115200);
	Serial.setTimeout(2000);
	// Wait for serial to initialize.
	while(!Serial) { }

	dht.afterRead(sendData);
//	myWifiManager.resetSettings();
	// collecting info from the user
	myWifiManager.addParameter(IOT_URL_PARAM_NAME, "Iot Server URL", "");
	myWifiManager.addParameter(IOT_API_KEY_PARAM_NAME, "API Key Write", "");
	myWifiManager.addParameter(DEEP_SLEEP_TIME_PARAM_NAME, "Interval Minutes", "");

	if (!myWifiManager.begin("WEATHER_TRA")) { // looks like something went wrong
		//myWifiManager.resetSettings();
		ESP.reset();
	}
	sleepMilliseconds = atoi(myWifiManager.getByKey(DEEP_SLEEP_TIME_PARAM_NAME))  * 60 * pow(10,6); // converting to microseconds
	iot.setApiKey(myWifiManager.getByKey(IOT_API_KEY_PARAM_NAME), WRITE);
	iot.setUrl(myWifiManager.getByKey(IOT_URL_PARAM_NAME));
	dht.read();
}

void loop() {
}

void sendData(FSEDHT *dht) {
	humidity = dht->getHumidity();
	temperature = dht->toCelsius();
	iot.setParameter("field1", String(temperature));
	iot.setParameter("field2", String(humidity));
	iot.write();
	Serial.print("H: ");
	Serial.print(humidity);
	Serial.print(", T: ");
	Serial.println(temperature);
#ifdef deep_sleep
	Serial.print("Going to sleep now for ");
	Serial.print(sleepMilliseconds);
	Serial.println(" Miliseconds");
	ESP.deepSleep(sleepMilliseconds);
#endif
}

This code also leverage the Wifimanager capability to collect data to be stored in SPIFFS. This all happens behind the scenes and we dont need to worry about it.

When this code runs for the first time or every time after you call function:

myWifiManager.resetSettings();

An access point network will be created and you can connect to it in order to setup your Wifi and the additional information for interaction with ThingSpeak Iot provider.

You may have noticed I did not add any code to the loop function. That is because I am using the deep sleep functionality and the code is supposed to run one time only and then put the device to sleep. When the device wakes up, the code will start from the beginning.

By the way, one of the fields I am collecting in the Wifi configuration is how many minutes I wish the device to sleep before it wakes up to make another read. I put it 3 minutes but you can adjust as per your needs

Step 4: ThingSpeak

ThingSpeak is a REST API Iot provider. The libraries I am using in my code abstract the interfaces with the API in a way that you dont need to fully understand how to make requests to write or read data from the provider. But, you will need to have some interaction with their website in order to create a channel to keep track of the data the module is sending and reading.

Once you create an account, you will be able to create a channel for your device. That channel will handle the data sent by the transceiver and it will also provide data to the receiver.

I will not go over the details of how to create an account and later create a channel on ThingSpeak. But you will need the following data to make this works:

  • Channel ID - To be set in the Receiver Module
  • Read API KEY - To be set in the Receiver Module
  • Write API KEY - To be set in the Transmitter Module

Those values will be asked when you set up your Wifi connection and they will be stored in the device. That way, 1 time is enough. Unless you change your wifi provider and the configuration for your Wifi connection needs to be reset.

Step 5: Adapting Receiver Code - Receiver

In the Part 1 of this tutorial, we created the receiver device and we added some code to read from a DHT11 and display it in the OLED display.

Now, along with its own peripheral sensor, the Receiver module will also get data from the ThingSpeak channel and also display in the OLED display.

For that, we had to make some modifications in the requirements and also some modifications in the code. Follow below, how is the final version of the platformio.ini file:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
;  https://docs.platformio.org/page/projectconf.html...

[env:esp8285]
platform = espressif8266
board = esp8285
framework = arduino
lib_ldf_mode = chain+
upload_resetmethod = nodemcu
board_build.f_cpu = 26000000L

lib_deps = 
	olikraus/U8g2 @ ^2.28.8
	arduino-libraries/NTPClient @ ^3.1.0
	fullstackelectron/FSEDTH11 @ ^1.0.0
	fullstackelectron/FSESPIFFS @ ^1.0.0
	fullstackelectron/FSEWifiManager @ ^1.0.0
	fullstackelectron/FSEThingSpeak @ ^1.0.0
upload_speed = 460800

and the code as below:

#include 

#include 
#include 
#include 
#include 


#include 
#include "FSEThingSpeak.h"
const long utcOffsetInSeconds = -18000; // -4 hours
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 2, /* data=*/ 0, /* reset=*/ U8X8_PIN_NONE);

FSEDHT dht(3);
FSEThingSpeak iot;

#define IOT_URL_PARAM_NAME "iot_thingspeak_url"
#define IOT_API_KEY_PARAM_NAME "iot_api_key"
#define IOT_CHANNEL_ID_PARAM_NAME "iot_channel_id"
#define IOT_TEMP_PARAM_NAME "field1" // humidity
#define IOT_HUM_PARAM_NAME "field2" // temperarure

#define MYSERIAL

FSEWifiManager myWifiManager;
void writeOled(float number);
void writeOled(String text);
void waitingWifiConfig(WiFiManager *wifiManager);
void wifiFailedMessage();
void readDHT();
void writeDataToOled();
void setData(FSEDHT *dht);
void error(FSEDHT_error_t error);
volatile float humidity=0.0, temperature=0.0;
volatile float ext_humidity=0.0, ext_temperature=0.0;
void saveConfig();
void readIot();

void setup() {
	//reseting wifi
//	myWifiManager.resetSettings();
#ifdef MYSERIAL
	Serial.begin(115200);
	Serial.setTimeout(2000);
	// Wait for serial to initialize.
	while(!Serial) { }

#endif
	pinMode(3, FUNCTION_3); // setting RX pin to FUNCTION_3
	u8g2.begin();

	u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
	writeOled("Connecting Wifi...");
	myWifiManager.addParameter(IOT_URL_PARAM_NAME, "Iot Server URL", "https://api.thingspeak.com");
	myWifiManager.addParameter(IOT_API_KEY_PARAM_NAME, "API Key", "");
	myWifiManager.addParameter(IOT_CHANNEL_ID_PARAM_NAME, "Channel Id", "");

	myWifiManager.setAPCallback(waitingWifiConfig);
	myWifiManager.setSaveConfigCallback(saveConfig);
	myWifiManager.begin("WEATHER_REC");

	if (WiFi.status() != WL_CONNECTED) {
		wifiFailedMessage();
	}

	dht.afterRead(setData);
	dht.onError(error);

	iot.setApiKey(myWifiManager.getByKey(IOT_API_KEY_PARAM_NAME), READ);
	iot.setUrl(myWifiManager.getByKey(IOT_URL_PARAM_NAME));
	iot.setChannelId(myWifiManager.getByKey(IOT_CHANNEL_ID_PARAM_NAME));
	iot.setParameter("field1", ""); // temperature
	iot.setParameter("field2", ""); // humidity

}

void saveConfig() {
	Serial.println("If we wish to do something after saving");
}

void error(FSEDHT_error_t error) {
	Serial.println("Error");
	Serial.println(error.errorNum);
	Serial.println(error.errorMsg);
}

void setData(FSEDHT *dht) {
	humidity = dht->getHumidity();
	temperature = dht->toCelsius();
	writeDataToOled();
}

String getTime() {
	//only will get next time if passed more than 30 secs
	static unsigned long last_update = 50000;
	static String lastTime;

	if (millis() - last_update > 30000) {
		timeClient.update();
		char buff[5];
		sprintf(buff, "%.2d:%.2d", timeClient.getHours(),timeClient.getMinutes());
		lastTime = buff;
	}
	return lastTime;
}

void readDHT() {
	dht.read();
}

String toString(float f) {
	char buff[100];
	dtostrf(f, 2, 2, buff);
	return buff;
}
void writeDataToOled() {
	u8g2.clearBuffer();
	u8g2.drawStr(100,10,getTime().c_str());
	u8g2.drawStr(30,25,"INT");
	u8g2.drawStr(0,40,"T");
	u8g2.drawUTF8(30, 40, toString(temperature).c_str());
	u8g2.drawUTF8(80, 40, toString(ext_temperature).c_str());
	u8g2.drawStr(0,60,"H");
	u8g2.drawStr(30,60, toString(humidity).c_str());
	u8g2.drawStr(80,60, toString(ext_humidity).c_str());
	u8g2.drawLine(65, 16, 65, 60);

	u8g2.drawLine(0, 29, 120, 29);

	u8g2.drawLine(15, 16, 15, 60);
	u8g2.drawStr(80,25,"EXT");

	u8g2.sendBuffer();
}
void wifiFailedMessage() {
	for (int i = 5; i > 0; i--) {
		u8g2.clearBuffer();
		u8g2.drawStr(0,10,"WIFI Failed");
		u8g2.drawStr(0,30,"Retrying in:");
		char buff[100];
		dtostrf(i, 2, 2, buff);
		u8g2.drawStr(0,50,buff);
		u8g2.sendBuffer();
		delay(1000);
	}
	ESP.reset();
}

void waitingWifiConfig(WiFiManager *myWiFiManager) {
	u8g2.clearBuffer();
	u8g2.drawStr(0,30,"Connect to SSID:");
	u8g2.drawStr(0,50,myWiFiManager->getConfigPortalSSID().c_str());
	u8g2.sendBuffer();
}

void readIot(){
	iot.readLatest();
	ext_temperature = iot.getByField("field1").toFloat();
	ext_humidity = iot.getByField("field2").toFloat();
}
void loop() {
	readDHT();
	readIot();
	writeDataToOled();
	delay(10000);
}

void writeOled(String text){
	u8g2.clearBuffer();          // clear the internal memory
	u8g2.drawStr(0,10,text.c_str());  // write something to the internal memory
	u8g2.sendBuffer();
}

Step 6: Conclusion

Now we can see both modules working together. You have a weather station that will monitor the temperature of 2 different areas. The very common application for that is using a device to monitor the temperature from outside and inside. Or maybe you want to know the temperature in your baby bedroom. Or anything else you could imagine.

The idea in this tutorial was to present the Iot concepts and how we can use it to create amazing things. Now, it is up to you to get this knowledge and create something great.

All the libraries used in this Tutorial can be found in github and in platformio registry. Please shoot me a message if anything is missing or if you stuck somewhere. I can either help you and improve this tutorial to make sure future readers would have the improved version.