# WxMate an IoT project for 1DT305
###### tags: `Espr32 BH1750 DT11 BMP270`
>Made by Jonas Bjork, Jb225hm
WxMate is a weather station application that collects information about current weather conditions and presents it to the user via a web interface.
This tutorial is made as a report for the IoT course at Linnéuniversitetet.
I will describe how to
* Setup computer for development
* Build the project
* Read data from sensors
* Send data to the server
* Host node-red for the presentation of data
The project will take roughly three hours if you have the previous knowledge of C, Docker and JavaScript.
### Objective
I have chosen to create a weather app, WxMate, to check weather conditions in the comfort of my phone. I have found myself walking across the apartment to see the current temperature. Now I have all the information just a click away.
During the project, I realised that IoT is an excellent way to expand your knowledge in applied science. Moreover, seeing my code work on a microcontroller and not just my main computer was exciting.
### :memo: Material
| Material | Details | Cost | Link |
| ---------- | --------------------------------- | -------- |---------------------|
| Esp32 | 32 bit micro controller | 94 kr |[:link:](https://sizable.se/P.CE9S1/ESP32) |
| BMP280 | Temp and pressure sensor | 34 kr |[:link:](https://sizable.se/P.73E2B/-BMP280-3.3V-Temperatur-och-trycksensor) |
| BH1750 | Lux sensor | 29 kr |[:link:](https://sizable.se/P.5BWJP/Digital-I2C-ljussensor-BH1750FVI) |
| DHT11 | Temp and humidity sensor | 28 kr |[:link:](Https://sizable.se/P.WGXR4/DHT11-Temperatur-och-luftfuktighetssensor) |
| Breadboard | Construction base for prototyping | 34 kr |[:link:](https://sizable.se/P.SFGYT/Kopplingsdack-med-400-punkter) |
| Cables | Cables to connect sensors | 24 kr |[:link:](https://sizable.se/P.K2HUW/Dupont-kopplingskablar-Hane-Hane-40-pin) |
| USB cable | Data transfer able USB cable | 18 kr |[:link:](https://sizable.se/P.VC1YX/Micro-USB-kabel-50-cm) |
| | TOTAL COST with shipping | 319 kr | |
[Esp32]:(https://sizable.se/P.CE9S1/ESP32)
[BMP280]:(https://sizable.se/P.73E2B/-BMP280-3.3V-Temperatur-och-trycksensor)
[BH1750]:(https://sizable.se/P.5BWJP/Digital-I2C-ljussensor-BH1750FVI)
[DHT11]:(Https://sizable.se/P.WGXR4/DHT11-Temperatur-och-luftfuktighetssensor)
[BreadBoard]:(https://sizable.se/P.SFGYT/Kopplingsdack-med-400-punkter)
[Cables]:(https://sizable.se/P.K2HUW/Dupont-kopplingskablar-Hane-Hane-40-pin)
[USB]:(https://sizable.se/P.VC1YX/Micro-USB-kabel-50-cm)
I have chosen to base my project on the ESP32. It is a cheap microcontroller that can manage the requirements I had for this project. I was very impressed with what a 94kr microcontroller can do. However, I have a long way to go to use it to its fullest potential.
### :computer: Computer setup
I chose to use [visual studio code](https://https://code.visualstudio.com/) (VS Code). Vs Code is a code editor with plenty of optional extensions that enhance and modify the editor's experience. It is an excellent choice of IDE. To code with the ESP32, I downloaded [PlatformIO](https://platformio.org/), which makes it possible to upload your code and connect to the device's serial port.
### Install my setup
To install VS Code on Linux, you type the following in the terminal
```bash=
sudo apt install code
```
When the installation is complete, type "code" and press enter.
Inside VS Code, you need to install PlatformIO. It is done by clicking
1. Extensions
2. Search for platform IO
3. Install PlatformIO

After that, you must do a new project by pressing "new project" and selecting your hardware.

To use sensors, you must first install them via libraries on PlatformIO.
1. Press libraries
2. Enter the name of the sensors and choose the corresponding library.

1. Press Add to project
2. Select the active project
3. Press Add

On the lower part of the screen in VS Code, you will find a toolbar. Here you can
1. Home for PlatformIO
2. Compile your code
3. Transfer code to ESP32
4. Serial port to see Serial.print in your terminal
5. Active project

Now you are ready to write some code.
> NOTE: If you like a more in-depth explanation of VS Code with PlatformIO, please watch [this](https://www.youtube.com/watch?v=JmvMvIphMnY&ab_channel=DroneBotWorkshop) excellent video from DroneBot Workshop.
### Putting everything together
To connect all sensors to the ESP32, I used a breadboard that makes it easy to test your setup. Due to the BMP280 and BH1700 having I2C capabilities, these were connected in series to pins D21(SDA) and D22(SCL). The DHT11 is connected to pin D4. All sensors use the same 3v3 power pin and GND pin. My setup was made for development. The next step would have been to solder on the cables and 3dprint a case and a sensor holder. Sadly was, all the 3dprinters broken at my school.

### Platform
I have chosen to use Node-red to interact with the data from the ESP32. Node-red has a graphical approach to building your server. It makes it easy to see how different parts are connected. Node-red is connected to a SQL database that saves all data. The database will make it possible to watch trends, averages and the collected data history.
In Figure 1, you can see the flow of the server. It consists of two parts, update-sensor and getsensordata. The getsensordata is a function that returns the latest recording from the sensors in the database. This can be used, for example, by another ESP32 with a screen to display the current temperature.

> Here I made an example of what getsonsordata can be used for (which will not be covered in this tutorial)
The second function is the update-sensor. When data arrives from the ESP32, it adds this information to the database and then updates the dashboard. This sequence ensures the dashboard has the latest data displayed at any given time.

Figure 1: Node-red flow
I have self-hosted this with the help of docker-compose on a remote server and exposed this to the internet. But in this tutorial, I will explain to do it on the localhost. Getting a remote server up and running and exposing ports with SSL certificates is out of scope for this tutorial.
#### Self-host on local computer
> This section is made for Linux users
The first step is installing docker, which will be done by typing this command in the terminal. It will download a script that will install docker.
```python=
curl -fsSL https://get.docker.com -o get-docker.sh
do sh get-docker.sh
```
Next, we need to install docker-compose. This is done with the following steps.
```bash =
mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v2.3.3/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
```
To verify docker-compose is installed, wright
```bash =
docker compose version
```
Should give an output something like this
```bash =
Docker Compose version v2.6.0
```
Now we need to create a docker-compose.yml file and paste the code below.
```dockerfile=
services:
node-red:
image: nodered/node-red:latest #image to be used
container_name: node-red #name for container
restart: always # If it shall restart
environment:
- TZ=Europe/Stockholm # Timezon
ports:
- "80:1880" # Expose 80 port to 1880 on container.
networks:
- node-red-net
volumes:
- ./node-red-data:/data # Data to be saved in subfolder
db: #database for node reds data
container_name: wxMate
image: mysql:latest
restart: always
environment: # Need to create users for the database
MYSQL_DATABASE: 'wxMate'
MYSQL_USER: 'bigboy'
MYSQL_PASSWORD: 'yourpassword'
MYSQL_ROOT_PASSWORD: 'yourpassword'
networks:
- node-red-net # connect to the same network as a node-red container
volumes:
- ./db:/var/lib/mysql
- ./init:/docker-entrypoint-initdb.d #where init database script must be
volumes:
node-red-data:
networks:
node-red-net:
```
With this done we navigate to the docker file in the terminal. There we need to make a folder and paste the init file for the database.
```bash=
mkdir init
cd init
vim 01.sql
```
Paste (vim command "CTRL+SHIFT+V") below code into 01.sql and save(vim command ":wq").
```sql=
CREATE TABLE wx (
time TIMESTAMP DEFAULT NOW(),
temperature DECIMAL(5,2),
pressure SMALLINT UNSIGNED,
lux SMALLINT UNSIGNED,
humidity DECIMAL(5,2)
```
Now we need to return to the docker-compose file and start the container.
```bash=
cd ..
sudo docker-compose up -d
```
The container is now running with the related database. You can access the node-red instance by entering localhost in your web browser.
When inside node-red, you must install two packages, node-red-dashboard and node-red-node-MySQL. This is done by pressing ALT+SHIFT+P, pressing install, and installing the above-stated packages.
Now we press CTRL+i and paste the [flows.json](https://github.com/JBetula/wxmateFlow) into the project.
The final step is to press Deploy up in the right corner.
Congrats! You now have a working backend.
### The Code
Finally, we have reached the code section.
This project is written in C and set up according to Arduino's style. First, we have a setup section that will run once every time the ESP32 is powered on. Then, the loop section is made as an infinity loop. This leads to the loop will continue to loop until it is interrupted. In our case, it shall only loop once because we like the ESP32 to enter deep sleep after weather information has been sent to save power. This code shall be entered into src/main.cpp.
```c=
#include <Arduino.h>
#include <Wire.h>
#include <HTTPClient.h>
#include <WiFi.h>
// Sensors
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
#include <BH1750.h>
#include <DHT.h>
// Macros
#define TEMPCORRECTION(t) (t - 0.0) // enter your corrections
#define PRESSURECORRECTION(p) (p - 0) // enter your corrections
#define SLEEP_TIME_IN_MINUTES(t) (t * 60000000)
#define DHTPIN 4
#define DHTTYPE DHT11
#define TIME_TO_SLEEP 5
// Functions
void connectToWifi();
void goToSleep();
void initSensors();
void getSensorData();
void ifConnectedSendData();
void bootUp();
// Struct to hold all data
typedef struct wxData
{
int pressure;
float temperature;
int lux;
float humidity;
} wxData_t;
// Variables to save in deepsleep
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR wxData_t wx;
// sensors init global
Adafruit_BMP280 bmp;
BH1750 luxMeter(0x23);
DHT dht(DHTPIN, DHTTYPE);
// Login credentials
const char *serverName = "localhost";
const char *ssid = "Name if wifi";
const char *password = "password to wifi";
// Main program
void setup()
{
bootUp();
initSensors();
}
void loop()
{
getSensorData();
connectToWifi();
ifConnectedSendData();
goToSleep();
}
// Initiate Serial and enable deepsleep
void bootUp()
{
Serial.begin(9600);
delay(2000); // Take some time to open up the Serial Monitor
++bootCount;
Serial.println("Boot number: " + String(bootCount));
esp_sleep_enable_timer_wakeup(SLEEP_TIME_IN_MINUTES(5));
}
// Initiate sensors
void initSensors()
{
Wire.begin();
dht.begin();
luxMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
bmp.begin(0x76);
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
}
// Extract sensor data and saves them to data struct
void getSensorData()
{
delay(1000);
wx.pressure = bmp.readPressure() / 100; // Value in hPa
wx.temperature = TEMPCORRECTION(bmp.readTemperature());
wx.lux = 0;
wx.humidity = PRESSURECORRECTION(dht.readHumidity());
if (luxMeter.measurementReady())
{
wx.lux = luxMeter.readLightLevel();
}
}
// Connect to wifi. After 10 unsuccessfully tries goes to deepsleep
void connectToWifi()
{
int tries = 0;
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
// Checks if WiFIi has connected
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.print(".");
++tries;
if (tries > 10)
{
Serial.println("Could not connect!!!");
goToSleep();
}
}
Serial.println("WiFi connected..!");
}
// If connected to WiFi sends a http post request.
void ifConnectedSendData()
{
if (WiFi.status() == WL_CONNECTED)
{
WiFiClient client;
HTTPClient http;
// Build the http request
http.begin(client, serverName);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=&temp=" + String(wx.temperature) + "&lux=" + String(wx.lux) + "&pressure=" + String(wx.pressure) + "&humidity=" + String(wx.humidity);
http.POST(httpRequestData);
http.end();
}
else
{
Serial.println("Not Connected to WiFi!!");
}
}
// Initiate sleep
void goToSleep()
{
Serial.println("Going to sleep now");
Serial.flush();
esp_deep_sleep_start();
}
```
:warning: You need to enter your wifi user information in rows 48-49.
If you find the above code hard to understand, then read the code below; this will give you enough understanding of what the program does :pray:
```c=
//MAIN PROGRAM
void setup()
{
bootUp();
initSensors();
}
void loop()
{
getSensorData();
connectToWifi();
ifConnectedSendData();
goToSleep();
}
```
### Transmitting the data / connectivity
The ESP32 is connected via WiFi to the internet. Every 5 minutes, it sends an update to the server via an HTTP post request.
### Presenting the data
The data is displayed via a node-red dashboard and can be accessed via the server's address with /ui at the end (localhost/ui).
The ui is made in such a way that you first see the current information of the sensor, followed by a chart with historical data. I find it interesting to see how the weather has been during the last few days. I find it useful to have a timestamp when the dashboard was last updated.





The data is sent every 5 minutes from the ESP32. When the data arrives, it will update the dashboard and make an entry into the database. I find that an accuracy of 5 minutes is long enough to save power and frequent enough to be accurate.
### Finalising the design

This project is a great way to learn more about IoT. It has been challenging at times, but I have managed to complete it.
You can view the project's final result on [wxMates hompage](https://wxmate.paltkoma.com/ui/). In addition, you can find the code on [github](https://github.com/JBetula/wxMatepublic/). I am pleased with the project's outcome, and I already have many new ideas that I like to explore in the IoT world.
The power consumption during deep sleep was the project's most significant setback and disappointment. I spent many hours searching for an answer to this problem. In the end, it came down to the board I bought being of a low quality. In the specification, ESP32 shall only draw 15µA at deep sleep, but my board draw 15mA. That made my battery life go from many years to just a month.
If I could start over, I would like to have used MQTT instead of HTTP requests and not buy the cheapest ESP32 board.