IoT weather station with Seeed Studio

Monitoring the environmental data using Seeed Studio XIAO ESP32C3, Arduino, and MQTT

Leonardo Cavagnis
13 min readFeb 8, 2023

I can’t change the direction of the wind, but I can adjust my sails to always reach my destination.

In this article, I will describe the creation of an IoT weather station using Seeed Studio devices, which is able to measure and display temperature and humidity levels. The station features an OLED screen that displays the current readings, as well as the ability to remotely access the data through a web platform using an MQTT server connection. This allows you to monitor the weather conditions in your area from anywhere with internet access.

This project is an excellent opportunity to learn more about IoT and weather monitoring, and how to use Seeed Studio products to build your IoT projects.

IoT weather station — SW architecture overview. Image by author.

The hardware setup

Seeed Studio is a company that specializes in producing hardware and software platforms. It offers a wide range of products, including development boards, modules, and sensors that can be used to build a variety of IoT projects.

Hardware things used in this project:

  • Microcontroller: Seeed Studio XIAO ESP32C3
    It is a development board based on the ESP32-C3 microcontroller produced by Espressif Systems. It is designed for low-power IoT applications with built-in Wi-Fi and Bluetooth capabilities, and it has a variety of other peripherals, such as I2C, SPI, UART, and PWM.
  • Peripherals: Seeed Studio XIAO Expansion Base
    It is an accessory board designed to be used with the Seeed Studio XIAO ESP32-C3 board. It provides additional functionality to the development board by adding extra peripherals, such as an OLED display, Real-Time Clock (RTC), expandable memory space, buzzer, button, and more. It also provides connectors, such as Grove connectors (for sensors and actuators) and a JST connector (for LiPo battery),
  • Environmental sensor: Seeed Studio Grove — Temperature & Humidity Sensor V2.0
    It is a sensor module designed for measuring temperature and humidity. It is part of the Seeed Studio Grove system, which is an open-source platform for connecting various sensors, actuators, and other devices in a modular and easy-to-use way.
  • Batteries:
    - 3.7V LiPo Battery with JST2.0 connector:
    Lithium-ion rechargeable battery to supply the microcontroller and the expansion board.
    - CR1220:
    Coin cell battery to supply the RTC chip.
  • Protective case: Acrylic Case for Seeed Studio XIAO Expansion board
    It is a protective enclosure made of acrylic material, designed for the Seeed Studio XIAO Expansion board.
Hardware setup. Image by author.

The firmware application

The IoT weather station is equipped with a firmware application written using Arduino IDE, that enables communication with an MQTT server via a Wi-Fi connection.
The device sends temperature and humidity readings from the environmental sensor to the MQTT server. Users can remotely view the environmental data through a web platform or directly on the device’s OLED display. The device also features a buzzer that will emit a sound signal in case of errors such as Wi-Fi or MQTT server connection issues and a button for switching between different display screens.
The OLED display shows information such as temperature, humidity, time and date, Wi-Fi and MQTT connection status, and the name of the connected Wi-FI network and MQTT server.

Sensors and peripherals used in the project. Image by author.

Arduino IDE setup

The XIAO ESP32C3 is a powerful and versatile microcontroller that is fully compatible with the Arduino platform. This means that it can be easily programmed using the Arduino IDE, allowing users to take advantage of the wide set of libraries and resources available for the Arduino ecosystem.

In this section, I will guide you through the process of setting up the Arduino IDE for use with the XIAO ESP32C3:

Arduino Preferences panel. Image by author.
  • Go to Tools ➜ Board ➜ Boards Manager and install the esp32 package
Arduino Boards Manager panel. Image by author.
  • Go to ToolsBoardESP32 Arduino and select XIAO_ESP32C3
  • Connect the XIAO ESP32C3 to the computer via a USB Type-C cable
  • Go to ToolsPort and select the serial port name of the connected XIAO ESP32C3
Arduino Tools menu: board and port. Image by author.
  • Now, you’re ready to write the application code!

Wi-Fi connectivity

XIAO ESP32C3 uses the ESP32-C3 Wi-Fi chip. This chip is based on the ESP32 Wi-Fi and Bluetooth SoC and offers support for dual-band Wi-Fi 802.11b/g/n/e/i.
Connecting to a Wi-Fi network using the standard WiFi library of Arduino is a straightforward process. The library is included in the Arduino IDE and does not require any additional installation.

Below you will find a code snippet that demonstrates how to use the library to connect to a Wi-Fi network using the SSID and password:

#include <WiFi.h> //Include the standard Wi-Fi library

//Set the SSID and password of the network you want to connect
const char* ssid = "<INSERT_YOUR_SSID>";
const char* password = "<INSERT_YOUR_PASSWORD>";

void setup() {
Serial.begin(115200);

Serial.print("Connecting to ");
Serial.println(ssid);

//Connect to the network
WiFi.begin(ssid, password);

//Checks the status of the connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

//Once the connection is established, print the assigned IP address
Serial.println("WiFi connected - IP address:");
Serial.println(WiFi.localIP());
}

void loop() {

}

Temperature and humidity reading

The Grove Temperature & Humidity Sensor V2.0 module is based on the DHT20 sensor, which is a digital sensor that utilizes capacitive humidity sensing and thermistor technology to measure temperature and humidity respectively. It can measure temperature range from -40°C to 80°C (accuracy: ±0.5 ℃) and humidity range from 0%RH to 100%RH (accuracy: ±3%RH).

The Arduino library to communicate with the module is called Grove Temperature And Humidity Sensor and is built by Seeed Studio. To install it, you will need to download the latest library version as a zip file from the “Releases” section of Seeed Studio’s Github page. You can install it manually by going to SketchInclude LibraryAdd .ZIP Library.

How to include library in Arduino. Image by author.

The code below allows you to read temperature and humidity values from the DHT20 sensor with a fixed interval of 1 second and print them to the serial monitor.

#include "DHT.h" //Include the Grove Humidity&Temperature sensor library

DHT dht(DHT20); //Select DHT20 sensor type

unsigned long currentMillis;
unsigned long previousMillis;

void setup() {
Serial.begin(115200);

Wire.begin(); //Initialize the Wire protocol. DHT20 uses I2C to communicate
dht.begin(); //Initialize DHT sensor

previousMillis = millis();
}

void loop() {
currentMillis = millis();

if (currentMillis - previousMillis >= 1000) {
float temp_hum_val[2] = {0};

//Read the temperature and humidity values from the sensor
if (!dht.readTempAndHumidity(temp_hum_val)) {
Serial.print("Humidity: ");
Serial.print(temp_hum_val[0]);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(temp_hum_val[1]);
Serial.println(" °C");
} else {
Serial.println("Failed to get temprature and humidity value.");
}

previousMillis = currentMillis;
}
}

In the example above, you don’t use the delay(ms) function to introduce a 1-second interruption because it blocks the execution of the code during the delay period. This means that while the delay is running, the microcontroller is not able to perform any other operations. Instead, you used the millis() approach. This is because it allows other actions to be performed while the delay is occurring.

Serial output of temperature and humidity reading. Image by author.

Show data on OLED display

The board features a 0.96” OLED display, which is a type of display that uses Organic Light-Emitting Diodes to produce text and graphics. The display has a resolution of 128x64 and it is connected to the microcontroller via the I2C interface. It can be controlled using the U8g2 library in Arduino.

The OLED display is handled by the SSD1306 controller and is managed as a matrix of pixels. As an extension of the previous example, the temperature and humidity values are also printed on the display using the OLED controller. The display is updated every 1 second with the latest readings.

#include <U8x8lib.h> //Include the U8g2 library
//...

#define WIRE_SDA_PIN 4
#define WIRE_SCL_PIN 5

//Initialize the SSD1306 controller of the display
U8X8_SSD1306_128X64_NONAME_HW_I2C oled(WIRE_SCL_PIN, WIRE_SDA_PIN, U8X8_PIN_NONE);

void setup() {
oled.begin(); //Initialize the communication with the display
oled.setFlipMode(0); //Set the display orientation (0=0°, 1=180°)
oled.setFont(u8x8_font_chroma48medium8_r); //Set the font of the text to be displayed
//...
}

void loop() {
//...
//Print the humidity value at position [2,2] of the display
oled.setCursor(2, 2);
oled.print("Humi(%):");
oled.print(temp_hum_val[0]);
//Print the temperature value at position [2,3] of the display
oled.setCursor(2, 3);
oled.print("Temp(C):");
oled.print(temp_hum_val[1]);
//...
}

The U8g2 library requires knowledge of the location of the I2C pins to function properly. As seen in the figure below, the I2C pins of the Expansion Board are located at position 4 (SDA) and 5 (SCL) respectively.

I2C pins of XIAO Expansion Board. Image by author.

Date and time management with RTC

The Real Time Clock (RTC) is a component that manages the time and date on a device. The RTC component that is used in the expansion board is the PCF8563: a low-power, real-time clock/calendar that communicates with the microcontroller via the I2C interface. The PCF8563 uses a CR1220 coin cell lithium battery as a power source for maintaining accurate timekeeping when the main battery (3.7V LiPo Battery) is disconnected or discharged.

An RTC needs to be configured with the correct time at the initial set-up or when the battery is replaced. This process can be automatized by utilizing the internet connection to retrieve the accurate time from a Network Time Protocol (NTP) server. NTP is a networking protocol used to synchronize the clocks of computer systems over a network.

The code shown initializes the RTC component using the PCF8563 Arduino library. It establishes a connection to an NTP server to retrieve the current date and time and sets them on the RTC memory.

#include <PCF8563.h> //Include the RTC library
#include "time.h" //Include time library to manipulate date and time

//Define the NTP server to use, the GMT offset, and the daylight offset
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 3600; //GMT+1 (Rome)
const int daylightOffset_sec = 3600;

PCF8563 rtc;

void setup() {
//[...]

//Get the current time and date from the NTP Server
struct tm timeinfo;
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
getLocalTime(&timeinfo);

rtc.init(); //Initialize the PCF8563 RTC component
rtc.stopClock();

//Sets the date and time retrieved from the NTP server
rtc.setYear((timeinfo.tm_year+1900)-2000); // tm_year=(YYYY-1900), setYear(YYYY-2000)
rtc.setMonth(timeinfo.tm_mon+1); //tm_mon = [0(jan)-11(dec)]
rtc.setDay(timeinfo.tm_mday);
rtc.setHour(timeinfo.tm_hour);
rtc.setMinut(timeinfo.tm_min);
rtc.setSecond(timeinfo.tm_sec);

rtc.startClock(); //Start the PCF8563 RTC component
}

void loop() {
//Retrieve the current time from the RTC
Time nowTime = rtc.getTime();

//[...]
}

Navigating display with button pressure

The OLED display on the IoT weather station has two pages of information. The first page displays the current temperature and humidity data in real-time. The second page displays the current time and date, the name of the connected WiFi network, and the name of the MQTT server to which the device is connected.

The pages can be scrolled by pressing the button present on the expansion board. Below is an example of how to implement the navigation between the pages:

#define BUTTON_PIN      3

int buttonState;
int buttonStatePrev;
bool page;

void setup(){
//[...]

//Initialize button pin as digital input
pinMode(BUTTON_PIN, INPUT_PULLUP);
buttonStatePrev = digitalRead(BUTTON_PIN);
page = true;
}

void loop(){
//Read the current state of the button
buttonState = digitalRead(BUTTON_PIN);

//Check if the button is pressed
if (buttonState == HIGH && buttonStatePrev == LOW) {
page = !page;
}
buttonStatePrev = buttonState;

//Display the selected page
if (page) {
//Page 1: Temperature&Humidity
//[...]
} else {
//Page 2: Date&Time, WiFi network name, MQTT server name
//[...]
}
}

Monitoring data with MQTT

To remotely access environmental data, we use an MQTT server. MQTT stands for Message Queuing Telemetry Transport and it is a lightweight messaging protocol that allows for the efficient transfer of data between devices in an IoT network. It is designed for use on low-bandwidth, high-latency networks, and uses a publish-subscribe model for communication.

In this project, the IoT weather station sends real-time temperature and humidity data to dedicated MQTT topics. A topic is a way to categorize messages, allowing for easy filtering and organization of data.

The MQTT broker used is ThingSpeak. It is an open-source platform for the Internet of Things, which allows users to collect, store, and analyze data from connected MQTT clients. It also allows for real-time visualization of data through a web interface.

To set up an MQTT server on ThingSpeak, you will need to create an account on the ThingSpeak website and then follow these steps:

  • Click on the “New Channel” button and create a new channel with the name (IoTWS_Channel) and the fields you want to collect data for (Temperature, Humidity). After creation, take note of the channel ID.
New Channel section in ThingSpeak. Image by author.
  • Go to the “Device” section, select “MQTT” and click on the “Add a new device” button. Choose a device name (IoTWS_SeeedStudio), select the channel you want to communicate to (IoTWS_Channel), and click on the “Add Device” button.
Add Device panel in ThingSpeak. Image by author.
  • Take note of the credentials (ID, Username, Password) that will appear in the window as they will be needed for the device to connect to the MQTT server.
MQTT Credentials of ThingSpeak. Image by author.

Here is a code snippet that shows how to connect to the created MQTT broker and send temperature and humidity data within the topics, using the official Arduino MQTT library:

#include <WiFi.h>
#include <ArduinoMqttClient.h> //Include the MQTT library

WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);

//MQTT broker address
const char mqtt_broker[] = "mqtt3.thingspeak.com";
int mqtt_port = 1883;

//MQTT credentials
const char mqtt_id[] = "<INSERT_YOUR_ID>";
const char mqtt_username[] = "<INSERT_YOUR_USERNAME>";
const char mqtt_pwd[] = "<INSERT_YOUR_PASSWORD>";

//MQTT topics
const char topic_temp[] = "channels/<INSERT_YOUR_CHANNEL_ID>/publish/fields/field1";
const char topic_hum[] = "channels/<INSERT_YOUR_CHANNEL_ID>/publish/fields/field2";

//[...]

void setup() {
//[...]
//Set MQTT credentials
mqttClient.setId(mqtt_id);
mqttClient.setUsernamePassword(mqtt_username, mqtt_pwd);

//Connect to the MQTT broker
if (!mqttClient.connect(mqtt_broker, mqtt_port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
}
Serial.println("You're connected to the MQTT broker!");
}

void loop() {
//Send MQTT keep alive to ensure the broker that the client is still connected
mqttClient.poll();

//[...]
//Send temperature data to the respective topic
mqttClient.beginMessage(topic_temp);
mqttClient.print(temp_hum_val[1]);
mqttClient.endMessage();
//Send humidity data to the respective topic
mqttClient.beginMessage(topic_hum);
mqttClient.print(temp_hum_val[0]);
mqttClient.endMessage();

//[...]
}

Using a buzzer to signal issues

To alert the user of any issues with the IoT weather station, an alarm sound is emitted from the passive buzzer mounted on the expansion board. The issues that are signaled are related to problems with the WiFi and/or MQTT connection.

A passive buzzer is a device that generates a sound when an alternating signal (PWM) is applied to it. Typically, the signal is generated at its resonant frequency, which is the frequency at which the sound of the buzzer is the loudest and clearest. This frequency is determined by the physical characteristics of the buzzer. The resonant frequency of this buzzer is 2700Hz.

We will not use any third-party library for controlling the buzzer. Instead, we will simply use the basic functions of Arduino to control a digital pin, generating an alternating signal with a fixed frequency and pulse width (duty cycle). It will produce a high and clear acoustic signal when an issue is detected.

//[...]
#define BUZZER_PIN A3
#define BUZZER_FREQ 370 //[us] 370us = 1/(Resonant freq) = 1/(2700Hz)
#define BUZZER_DURATION 500 //[ms]

void setup() {
//[...]
pinMode(BUZZER_PIN, OUTPUT); //Initialize the pin connected to the buzzer as output

//[...]
int wifiConnAttempts = 0;
while (WiFi.status() != WL_CONNECTED) {
//[...]
wifiConnAttempts++;
if (wifiConnAttempts > 10) {
//If the WiFi connection procedure fails after 10 attempts, emit an acoustic signal
while (1) {
playBuzzerTone(BUZZER_FREQ, BUZZER_DURATION);
delay(200);
}
}
}

//[...]
if (!mqttClient.connect(broker, port)) {
//[...]
//If MQTT connection procedure fails, emit an acoustic signal
while (1) {
playBuzzerTone(BUZZER_FREQ, BUZZER_DURATION);
delay(200);
}
}

//[...]
}

void loop() {
//[...]
}

/*
* Emit an acoustic signal using a passive buzzer attached to a digital pin
* Input:
* frequency: frequency period in microseconds (us) of the acoustic signal
* duration: duration in milliseconds (ms) of the acoustic signal
* Output: None
*/
void playBuzzerTone(int frequency, int duration) {
for (long i = 0; i < duration * 1000L; i += frequency) {
digitalWrite(BUZZER_PIN, HIGH);
delayMicroseconds(frequency/2);
digitalWrite(BUZZER_PIN, LOW);
delayMicroseconds(frequency/2);
}
}

Let’s look at the system in action!

Now that all the parts of the code are ready, we can put everything together and upload it to the ESP32-C3 microcontroller. After that, verify that the system correctly connects to the WiFi network and MQTT server, by checking that the IoT weather station doesn’t emit error acoustic signals and that the temperature and humidity data appear on the OLED display.

With ThingSpeak, we can create graphical interfaces to display data coming from the device. To create the charts for temperature and humidity, follow these instructions:

  • Go to ChannelMyChannels and select your channel (IoTWS_Channel)
  • Click on the “Add Visualizations” button, select “Field1 Chart” and “Field2 Chart” and click on the Save button.

We are now ready to see the system in action: the two charts will display the temperature and humidity trends over time.

ThingSpeak dashboard. Image by author.

Next steps

This project can serve as a good starting point for a more sophisticated and feature-rich weather station. With the XIAO expansion base, you can add additional Grove sensors and actuators, such as a sunlight sensor to enhance the functionality of the system (for example, for monitoring outdoor light conditions and triggering watering logic through an actuator like a relay).

Seeed Studio has a rich collection of sensors, actuators, development boards, software, and more. Additionally, their website features a comprehensive wiki with a lot of examples and explanations.

Find all the project code here!

--

--

Leonardo Cavagnis
Leonardo Cavagnis

Written by Leonardo Cavagnis

Passionate Embedded Software Engineer, IOT Enthusiast and Open source addicted. Proudly FW Dev @ Arduino