Skip to main content

7.1 Smart Weather Station

This is the first project rather than an exercise, and the difference is that nothing in it is new. The sensor is the one from section 5, the display is the one from section 4, and the Wi-Fi is the one from section 6. What is new is that all three run at once, in one sketch, on one pair of wires: a board that measures the room, shows the reading on its own screen, and puts the same numbers on the internet where you can read them from anywhere.

That combination is what people mean by IoT: a small thing that senses something local and reports it somewhere else.

In this documentation you will learn:

  • How to put two Qwiic modules on one bus, and why they do not interfere with each other.
  • What an I2C address is for, in the one situation where it finally matters.
  • How to send a measurement to a web server with an HTTP POST request.
  • Why the display stays blank for the first thirty seconds, and why that is not a fault.
  • Why the same reading is printed three different ways in the same sketch.

Hardware required:

  • 1x Soldered NULA MINI board
  • 1x Breadboard
  • 1x Soldered SHTC3 temperature and humidity sensor
  • 1x 16x2 LCD display with I2C adapter
  • 2x Qwiic cables
  • 1x USB-C cable
ℹ️
Still no resistors and no jumper wires. Both modules are Qwiic, so the entire circuit is two cables that only fit one way round. The one thing to check before you start is that you have two Qwiic cables rather than one. Every earlier example needed only a single cable.

Putting the components together

Five steps, and three of them are just plugging a cable in.

1. Insert the NULA MINI board on the breadboard

Push the board into one end of the breadboard so that its two rows of pins sit on either side of the centre channel. In the photos it occupies rows 25 to 30.

NULA MINI board seated on the breadboard
Step 1: the board seated on the breadboard
ℹ️
As in examples 3.1, 4.1 and 5.1, the breadboard does no electrical work in this project. Nothing connects to the board's pins at all. It is there to hold the board flat while you push two cables into it.

2. Plug the first Qwiic cable into the board

The NULA MINI has one Qwiic connector, on the edge of the board between the USER and RST buttons, marked qwiic on the silkscreen. Push the cable in until it clicks.

Qwiic cable plugged into the NULA MINI board, with the other end still loose
Step 2: the first cable in the board's Qwiic connector, other end still free
⚠️
Do not confuse it with the smaller two-contact connector on the opposite edge, marked - and +. That one is the battery connector, and a Qwiic cable will not fit it.

If you would like to see exactly which connector this is on a bare board, it is highlighted here:

The NULA MINI board with its Qwiic connector highlighted
The Qwiic connector on the NULA MINI, highlighted in blue

3. Plug the other end into the SHTC3 sensor

The sensor is the small purple board silkscreened SHTC3 BREAKOUT. It has two Qwiic connectors, one at each end. Plug the cable into either one, and leave the other free. This is the first time in the kit that the spare connector actually gets used.

Qwiic cable running from the NULA MINI board to the SHTC3 sensor
Step 3: the first cable joining the board to the sensor

4. Chain the display onto the sensor

Take the second Qwiic cable. Plug one end into the SHTC3's free connector, and the other into either of the two connectors on the purple I2C LCD ADAPTER on the back of the display.

The full Qwiic chain: NULA MINI on the breadboard, a cable to the SHTC3 breakout, and a second cable from the SHTC3 to the I2C LCD adapter on the back of the display
Step 4: the display chained onto the sensor with the second cable. That is the entire circuit

This is the only genuinely new connection in the project, and it is worth being clear about what it is not. The second cable does not run the display through the sensor, with the sensor passing data along. Both connectors on the SHTC3 are wired to the same four conductors: they are one junction with two sockets. Chaining simply saves you needing a board with three Qwiic connectors on it.

ℹ️
Because of that, the order does not matter. Board to display, then display to sensor, works exactly as well. So does plugging both modules into a Qwiic hub, if you have one. All four conductors end up joined either way.

5. Connect the USB-C cable

Plug the USB-C cable into the board and into your computer. The purple PWR light comes on, and both modules are powered too. Everything on the chain takes what it needs through those same two cables.

The finished build powered up before uploading: the display backlight is lit and its top row shows sixteen solid blocks
Step 5: powered up, before uploading. Backlight on, top row full of blocks

That row of solid blocks is worth a moment. It is what an HD44780 display shows when it has power but has never been initialized, the state it wakes up in before any sketch has spoken to it. You saw the other version of this in example 4.1, where the screen was lit but completely empty. Either appearance means the same thing: the display has power, and nothing beyond that can be concluded yet.


Two modules, one pair of wires

Every Qwiic example so far has used a single module, which meant the address printed on these boards never mattered. Here it does.

I2C is a bus. The two signal wires are shared by everything plugged into them, so when the board sends a byte, both the sensor and the display receive it. What stops the chaos is that every message begins with an address, and a module only reacts to messages carrying its own:

ModuleAddressWhere it is written
16x2 LCD, through its adapter0x20I2C ADDR 0X20, on the purple adapter
SHTC3 sensor0x70not printed, fixed inside the chip

0x20 and 0x70 are different, so the two modules share one pair of wires and neither ever answers for the other. That is the whole trick, and it is why nothing in the sketch has to be told about the chaining. lcd.begin() and shtc3.begin() are written exactly as they were in 4.1 and 5.1.

⚠️
Two modules with the same address on one bus is the situation that does not work: both answer at once and neither reading is trustworthy. That is what the A0, A1 and A2 pads on the LCD adapter are for: bridging them moves the display up to 0x27, so a second identical display could join the same cable. You will not need them here, since 0x20 and 0x70 were never going to collide.

Setting up webhook.site

The sketch needs somewhere on the internet to send its readings, and webhook.site provides one for free with no account.

  1. Open webhook.site in a browser. It generates a unique URL for you the moment the page loads.
  2. Copy the address shown under Your unique URL. It looks like https://webhook.site/fce7fced-1f3b-4ea1-8ed7-498130bbe714.
  3. Leave that browser tab open. Every reading your board sends will appear in it.
A fresh webhook.site page showing the unique URL and an empty inbox waiting for the first request
A fresh webhook.site page. The inbox reads 0/50 and is waiting for the first request

Then paste it into the sketch, replacing the placeholder text between the quotation marks:

const char* webhookURL = "https://webhook.site/your-unique-id";

While you are there, fill in your network details too:

const char* ssid = "your ssid";
const char* password = "your password";
⚠️
Three placeholders, and forgetting any one of them fails in a different way. A wrong password leaves the board printing dots forever with no error at all, exactly as in example 6.1. A wrong URL still connects and still returns a response code, just from somewhere that is not your page. Only the sensor tells you plainly when it is wrong.
ℹ️
Two limits on the free page, both visible in the screenshots. It keeps requests for 7 days, which is the 7d badge beside the URL, and it holds 50 of them, which is the 0/50 counter. At one reading every thirty seconds, fifty requests is about 25 minutes before it quietly stops recording new ones.

The first thirty seconds

There is one moment in this project that looks broken and is not, so it is worth knowing about before you upload rather than after.

Once setup() finishes, the sketch clears the display and drops into loop(). But a reading is only taken when millis(), the time since power-on, has moved UPDATE_MS past the last one, and lastUpdate starts at zero. So the first reading always lands thirty seconds after power-on, however quickly the rest of setup went.

In between, the display is lit and completely blank. It looks precisely like the contrast fault from example 4.1. It is not. Wait it out, and Temp: and Hum: appear together.

ℹ️
If you would rather not wait while testing, lower UPDATE_MS to something like 5000. Put it back before leaving the board running, though: at five seconds a reading, the free webhook page fills its fifty slots in about four minutes.

The same reading, three ways

One measurement leaves the board by three different routes in this sketch, and each formats it differently. Seeing why is the quickest way to understand what is actually being sent.

Serial.print(temperature, 2); // 27.93
lcd.print(temperature, 1); // 27.9
String postData = "temperature=" + String(temperature, 2); // temperature=27.93

The Serial Monitor gets two decimal places, because there is no shortage of room. The display gets one, because a 16-character row has to hold Temp: , the number, a degree symbol and a C. The POST gets two, because those numbers are going to a computer rather than to a person, and may as well arrive at full precision.

The degree symbol is the other difference:

Serial.print(" °C, Humidity: "); // a real ° character
lcd.print((char)223); // character number 223 in the display's own set

Your computer and the HD44780 do not use the same character set. Writing ° straight to the display would produce the wrong symbol, so the sketch asks for character 223 instead, which is where the degree sign lives in the display's built-in font.

ℹ️
There is a fourth route worth noticing: during the Wi-Fi wait the sketch prints a dot to both outputs. On the Serial Monitor the dots build into a row, because each print continues where the last one stopped. On the display they do not, because lcd.setCursor(0, 1) sends the cursor back to the start of the row every time, so you get a single dot that sits there and never moves.

Code

/**
**************************************************
*
* @file 7.1_Smart_Weather_Station.ino
* @brief Project that brings together three things you have already learned separately: the SHTC3 temperature
* and humidity sensor from section 5, the LCD display from section 4, and the Wi-Fi connection from
* section 6. The board measures temperature and humidity, shows them on the LCD, and sends them to a
* webhook on the internet so you can follow the readings from anywhere.
* For details, connection diagram and more, check out the example documentation at: <link placeholder>
* @author Soldered
***************************************************
*/

/*
The WiFi library contains everything needed to join a Wi-Fi network.
*/
#include <WiFi.h>

/*
The HTTPClient library lets us speak HTTP, so we can send our readings to a server.
*/
#include <HTTPClient.h>

/*
The Soldered library for the SHTC3 temperature and humidity sensor.
*/
#include "SHTC3-SOLDERED.h"

/*
The Soldered library for the LCD display.
*/
#include "LCD-SOLDERED.h"

/*
These two variables hold the name of your Wi-Fi network (the SSID) and its password. Replace the text between the
quotation marks with your own network details.
*/
const char* ssid = "your ssid";
const char* password = "your password";

/*
This variable holds the address we send our readings to. Open https://webhook.site in a browser, copy the unique link
it shows you, and paste it between the quotation marks below. Keep that browser tab open and you will see every
reading appear in it.
*/
const char* webhookURL = "your unique url";

/*
Here we create our two objects: one for the sensor and one for the display. An object is our way of talking to a
device: every function we call on it, we call through its name.
The sensor needs no pin numbers because it uses easyC, which is Soldered's name for an I2C connection over a single
cable, and I2C always uses the same two pins on the board (IO6 and IO7 on the NULA board).
*/
SHTC3 shtc3;
LCD lcd(16, 2);

/*
These two variables hold the latest readings. They are decimal numbers, which is why they are floats.
*/
float temperature = 0.0;
float humidity = 0.0;

/*
This variable remembers the moment of the last reading, and the one below it defines how much time passes between two
readings, in milliseconds. 30000 milliseconds is thirty seconds. Feel free to experiment with this value, but keep in
mind that every reading is also sent over the internet.
*/
unsigned long lastUpdate = 0;
const unsigned long UPDATE_MS = 30000;

void setup() {

/*
Serial.begin() establishes serial communication between your board and your computer via a USB cable. In a project
this size the Serial Monitor is our main tool for finding out what went wrong, so we start it first.
The short delay after it gives the connection a moment to settle, so the first messages are not lost.
*/
Serial.begin(115200);
delay(500);

/*
Here we prepare the display. begin() starts the communication with it, backlight() turns on its light so the text is
readable, and clear() wipes anything that was left on the screen from before.
setCursor() then chooses where the next text will appear: the first number is the column and the second is the row,
and both start counting at zero. So (0, 0) is the top left corner and (0, 1) is the start of the second line.
We show a short greeting first, so we can tell at a glance that the display itself works.
*/
lcd.begin();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Weather Station");
lcd.setCursor(0, 1);
lcd.print("Starting...");
delay(1000);
lcd.clear();

/*
begin() on the sensor starts the I2C communication and tells us whether the sensor answered, returning true on
success and false on failure. The "!" in front means "not", so this reads as "if the sensor did not start".
If the sensor is missing there is nothing left for this project to measure, so instead of continuing we print the
problem and stop here. The while(1) loop below never ends, which is a simple way of saying "go no further".
*/
if (!shtc3.begin()) {
Serial.println("SHTC3 init failed!");
lcd.print("SHTC3 error!");
while (1) delay(100);
}

/*
WiFi.begin() starts the connection attempt. The function only starts the process, it does not wait for it to finish,
so we tell the user what is going on both on the Serial Monitor and on the display.
*/
Serial.print("Connecting to Wi-Fi");
lcd.setCursor(0, 0);
lcd.print("Connecting WiFi");
WiFi.begin(ssid, password);

/*
Here we wait for the connection ourselves. WiFi.status() tells us the current state and WL_CONNECTED is the value it
reports once we are online.
*/
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.setCursor(0, 1);
lcd.print(".");
}

/*
WiFi.localIP() returns the address the router handed out to our board.
*/
Serial.println();
Serial.println("Wi-Fi connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());

//Let the user know we are online, then clear the display so the readings start on an empty screen.
lcd.clear();
lcd.print("WiFi Connected!");
delay(800);
lcd.clear();

Serial.println("Smart Weather Station ready!");
}

void loop() {

/*
millis() is a function that returns the number of milliseconds passed since the board began running the current
program. We use it instead of delay() so the board stays free to do other work between readings.
*/
unsigned long now = millis();

/*
Here we check how much time has passed since the last reading. Only when UPDATE_MS milliseconds have gone by do we
take a new one, and we immediately remember the current time as the new starting point.
*/
if (now - lastUpdate >= UPDATE_MS) {
lastUpdate = now;

/*
sample() tells the sensor to perform a fresh measurement, and the two read functions then hand us the results.
We have to call sample() first, otherwise we would keep getting the previous measurement.
*/
shtc3.sample();
temperature = shtc3.readTempC();
humidity = shtc3.readHumidity();

//Print the readings to the Serial Monitor. The number 2 tells the function how many decimal places to show.
Serial.print("Temperature: ");
Serial.print(temperature, 2);
Serial.print(" °C, Humidity: ");
Serial.print(humidity, 2);
Serial.println(" %");

/*
Now we show the same readings on the display. We clear it first, because writing shorter text over longer text
would leave leftover characters behind.
The (char)223 is the character code the display uses for the degree symbol. The Serial Monitor and the LCD do not
use the same character set, which is why we write the degree sign one way above and another way here.
*/
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(temperature, 1);
lcd.print((char)223);
lcd.print("C");

lcd.setCursor(0, 1);
lcd.print("Hum: ");
lcd.print(humidity, 1);
lcd.print(" %");

/*
Before sending anything we check that we are still online. A Wi-Fi connection can drop at any time, and trying to
send data without one would only waste time and print errors.
*/
if (WiFi.status() == WL_CONNECTED) {

/*
Here we create our HTTP client object, prepare the request with begin(), and describe the format of our data
with addHeader(). A header tells the server something about the request itself.
*/
HTTPClient http;
http.begin(webhookURL);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");

/*
Here we build the data we are going to send. The format "name=value&name=value" is the same one a browser uses
when you submit a simple web form: the ampersand ("&") separates one value from the next, and String() converts
our numbers into text so they can be joined to the rest with a plus sign.
*/
String postData = "temperature=" + String(temperature, 2) + "&humidity=" + String(humidity, 2);

/*
POST() sends the request together with our data and waits for the answer. It returns a response code: numbers of
200 and above mean the server answered, while a negative number means we never got an answer at all.
*/
int httpCode = http.POST(postData);

if (httpCode > 0) {
Serial.print("POST successful! Response code: ");
Serial.println(httpCode);
} else {

//errorToString() turns the negative error code into a short sentence that is easier to understand.
Serial.print("POST failed. Error: ");
Serial.println(http.errorToString(httpCode));
}

/*
end() closes the connection and frees the memory the client was using. Always close a connection once you are
done with it, otherwise a program that makes many requests will slowly run out of memory.
*/
http.end();
} else {

//If we lost the connection, start a new attempt and try again on the next reading.
Serial.println("Wi-Fi disconnected. Trying to reconnect...");
WiFi.begin(ssid, password);
}
}
}

What you should see

Upload the sketch, then open the Serial Monitor and set it to 115200 baud. Keep your webhook.site tab open beside it. There are three places to watch, and they should agree with each other.

On the display

After the Weather Station / Starting... greeting, the Connecting WiFi message, and the thirty-second wait, the readings appear:

The 16x2 display showing Temp: 27.9 degrees C on the top row and Hum: 40.8 percent on the bottom row
The finished weather station: 27.9 °C and 40.8 % relative humidity

Breathe gently on the sensor from a few centimetres away and the humidity figure climbs within a second or two, but you will only see it on the next reading, up to thirty seconds later. That delay is the clearest reminder that the display is not connected to the sensor in any live sense: it is showing whatever the last trip round loop() put there.

On the Serial Monitor

Serial Monitor at 115200 baud showing the Wi-Fi connection dots, the assigned IP address, the ready message, one temperature and humidity reading and a successful POST
One full cycle: connect, report the IP, take a reading, send it

Reading down from the blank line:

  • Connecting to Wi-Fi............ shows twelve dots, one every 500 ms, so this board took about six seconds to join the network. The row of dots is a usable stopwatch.
  • IP: 192.168.75.75 is the address your router handed the board. It is a private address, only meaningful inside your own network.
  • Smart Weather Station ready! is the last line of setup(). Everything after this comes from loop().
  • Temperature: 27.93 °C, Humidity: 40.80 % is the reading, at two decimal places. Compare it with the display photo above: 27.93 shown as 27.9, 40.80 shown as 40.8. Same measurement, two formats.
  • In POST successful! Response code: 200, the 200 is the HTTP code for "fine, I have it".
ℹ️
The lines above the blank gap in this screenshot are not from this sketch. They are the tail of the HTML page left over from example 6.1, still sitting in the Serial Monitor's scrollback. Uploading a new sketch does not clear the window. If you are unsure where your own output starts, press the Serial Monitor's clear button before resetting the board.

On webhook.site

The webhook.site page showing two received POST requests thirty seconds apart, with the form values temperature 27.93 and humidity 40.80
Two readings arrived, thirty seconds apart. The public IP address has been painted over

This page has more to say than the Serial Monitor does:

  • The timestamps are 10:58:40 and 10:59:10: exactly thirty seconds apart. That is far better proof that UPDATE_MS is doing its job than anything printed on the board itself.
  • Form values lists temperature and humidity as separate named fields. They are only broken out like that because of http.addHeader("Content-Type", "application/x-www-form-urlencoded"). Without that header the server would receive the same characters and have no idea they were meant as a form.
  • Raw Content shows what was actually sent: temperature=27.93&humidity=40.80. That is exactly the string postData was built from, and it matches the Serial Monitor line character for character.
  • Size: 32 bytes is that string's length. Count it if you like: the & and the = signs are part of the payload too.
  • user-agent: ESP32HTTPClient is how HTTPClient introduces itself. Your board announced what it was without being asked.
ℹ️
The Host field is greyed out in this screenshot on purpose. It shows the public address of the router the board sits behind, not the 192.168.75.75 the board printed. Both are correct: your router replaces the private address with its own public one on the way out, a process called NAT, and the server on the far side can only ever see the router. Webhook.site puts Whois and Shodan links beside it, which is a fair reminder that a public IP is worth being careful with.
⚠️
If you see SHTC3 init failed! on the display and in the Serial Monitor, the sketch stops there deliberately and never reaches the Wi-Fi part. Check the Qwiic cables are pushed fully home at all four ends, then check that Soldered NULA Mini ESP32C6 is the selected board. Picking any other board sends the I2C traffic to the wrong pins, and it still compiles and uploads perfectly.
⚠️
If the display stays blank but the Serial Monitor is printing readings normally, the display's contrast trimpot is turned down. It is the small screw on the purple adapter beside the CONTRAST silkscreen. Turn it slowly with a screwdriver until the characters appear. See 4.1 Print Message for the full description of this fault, but remember to rule out the thirty-second wait first.

Where to take it next

Everything in this project is one variable away from being something else:

  • Change UPDATE_MS and you change how often the room is sampled.
  • Swap the two lcd.print lines and the display shows humidity first.
  • Add a third name and value to postData and the webhook page grows a third form field.

The one thing you cannot do from here is read the data back: webhook.site shows you requests as they arrive, but it is not a place to keep them. Sending the same POST to a spreadsheet service or a database instead is the natural next step, and nothing about the board or the wiring would have to change.


Full example

Check out the full example code on the link below:

7.1_Smart_Weather_Station.ino

Project that measures temperature and humidity with the SHTC3 sensor, displays the readings on a 16x2 LCD, and sends them to webhook.site every 30 seconds.