The weather station measures temperature and humidity both indoors and outdoors. The station is based on an article from majsterkowo.pl by user r.blaszczak Solar weather station using Wemos D1 mini Pro and Raspberry Pi 3 B+. Of course, the project has been modified according to personal ideas.
Project Assumptions:
- Measurement of temperature and humidity both indoors and outdoors
- Saving measurements on a local server
- Displaying the latest measurement and history in the form of a graph on the website
- Building an application for the Android system
- Displaying the latest measurement as a widget for Windows 10
The power supply for the station, unlike what is described in the article, is provided by the electrical grid. Wires from a network cable (so-called “twisted pair”) approximately 2 meters long were used to connect the sensors to the ESP8266.
The final view of the website is shown in the image below.
Website link
The view of the widget in Windows 10.

The view of the Android application.

Used Components:
- Raspberry Pi 4 (using the version with 4 GB of RAM)
- ESP8266
- DTH11 temperature and humidity sensor
- DTH22 temperature and humidity sensor
On the Raspberry Pi, we install the Apache web server with PHP support and a database; for this project, I used MariaDB. Additionally, it is recommended to install an SSL certificate; Let’s Encrypt was used in this project to generate the certificate. Next, we create our database; the following code was used in the project to create the table.
CREATE TABLE S_01
(id
int(20) NOT NULL AUTO_INCREMENT,timestamp
timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,temperatureIN
float NOT NULL,humidityIN
float NOT NULL,temperatureOUT
float NOT NULL,humidityOUT
float NOT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The next step is to create files in the Apache main directory that are responsible for saving data to the database and displaying the current data on the webpage along with the history chart.
espdata.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?php include('config.php'); $w_api_key = $_GET['api_key']; $table = $_GET['station_id']; $tin = $_GET['tIN']; $hin = $_GET['hIN']; $tout = $_GET['tOUT']; $hout = $_GET['hOUT']; $conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name); if ($w_api_key == $write_api_key ) { //w tym miejscu przygotowanie JSON i zapisanie do pliku $date = date('Y-m-d H:i:s', time()); $array = array('time' => $date,'tIN' => $tin,'hIN' => $hin,'tOUT' => $tout, 'hOut' => $hout); $fp = fopen('readmeteo.json', 'w'); fwrite($fp, json_encode($array, JSON_PRETTY_PRINT)); fclose($fp); //wysłanie zapytania do bazy danych - zapisujemy dane otrzymane z ESP8266 $result = mysqli_query($conn, "INSERT INTO $table(temperatureIN, humidityIN, temperatureOUT, humidityOUT) VALUES ($tin, $hin, $tout, $hout)"); if ($result === false){ echo "ERR"; $conn -> close(); } else { echo "OK"; $conn -> close(); } } else { echo "Niepoprawny API-KEY"; } ?> |
A website presenting current data and the history chart.
meteochart.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
<?php include('config.php'); $w_api_key = $_GET['api_key']; $table = $_GET['station_id']; $dataPoints1 = array(); $dataPoints2 = array(); $dataPoints3 = array(); $dataPoints4 = array(); $conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name); if ($w_api_key == $write_api_key ) { $result = mysqli_query($conn, "SELECT * FROM $table ORDER BY id ASC"); if ($result === false){ echo "ERR"; $conn -> close(); } else { while ($row = mysqli_fetch_assoc($result)) { // Important line !!! Check summary get row on array .. foreach ($row as $field => $value) { // I you want you can right this line like this: foreach($row as $value) { switch ($field) { case "temperatureIN": $tIN_y = $value; break; case "temperatureOUT": $tOUT_y = $value; break; case "humidityIN": $hIN_y = $value; break; case "humidityOUT": $hOUT_y = $value; break; case "timestamp": $time = $value; break; } array_push($dataPoints1, array("label" => $time, "y" => $tIN_y)); array_push($dataPoints2, array("label" => $time, "y" => $tOUT_y)); array_push($dataPoints3, array("label" => $time, "y" => $hIN_y)); array_push($dataPoints4, array("label" => $time, "y" => $hOUT_y)); } } $conn -> close(); } }; ?> <!DOCTYPE HTML> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> <script> window.onload = function () { var chart = new CanvasJS.Chart("chartContainer", { theme: "light2", title: { text: "Historia temperatur, wilgotnoĹ›ci w domu i na zewnÄ…trz" }, subtitles: [{ text: "temperatura w stopniach celsjusza, wilgotność w %" }], legend:{ cursor: "pointer", itemclick: toggleDataSeries }, toolTip: { shared: true }, data: [{ type: "stackedArea", name: "Temperatura na zewnÄ…trz", showInLegend: true, visible: true, yValueFormatString: "#,##0 stopni Celsjusza", dataPoints: <?php echo json_encode($dataPoints2, JSON_NUMERIC_CHECK); ?> }, { type: "stackedArea", name: "Temperatura w domu", showInLegend: true, visible: true, yValueFormatString: "#,##0 stopni Celsjusza", dataPoints: <?php echo json_encode($dataPoints1, JSON_NUMERIC_CHECK); ?> }, { type: "stackedArea", name: "Wilgotność w domu", showInLegend: true, visible: true, yValueFormatString: "##,##0 ", dataPoints: <?php echo json_encode($dataPoints3, JSON_NUMERIC_CHECK); ?> }, { type: "stackedArea", name: "Wilgotność na zewnÄ…trz", showInLegend: true, visible: true, yValueFormatString: "##,##0 ", dataPoints: <?php echo json_encode($dataPoints4, JSON_NUMERIC_CHECK); ?> }] }); chart.render(); function toggleDataSeries(e){ if (typeof(e.dataSeries.visible) === "undefined" || e.dataSeries.visible) { e.dataSeries.visible = false; } else{ e.dataSeries.visible = true; } chart.render(); } } </script> </head> <body> <?php $w_api_key = $_GET['api_key']; $table = $_GET['station_id']; $conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name); if ($w_api_key == $write_api_key ) { $result = mysqli_query($conn, "SELECT * FROM $table where id = (select MAX(id) from $table)"); if ($result === false){ echo "ERR"; $conn -> close(); } else { echo "<div>"; while ($row = mysqli_fetch_assoc($result)) { // Important line !!! Check summary get row on array .. foreach ($row as $field => $value) { // I you want you can right this line like this: foreach($row as $value) { switch ($field) { case "temperatureIN": echo "<center><h1>Temperatura w domu</h1><h2><i class='fas fa-thermometer-half' style='color:#059e8a;'></i> ".$value." °C</h2></center>"; break; case "humidityIN": echo "<center><h1>Wilgotność w domu</h1><h2><i class='fas fa-tint' style='color:#00add6;'></i> ".$value." %</h2></center>"; break; case "temperatureOUT": echo "<center><h1>Temperatura na zewnÄ…trz</h1><h2><i class='fas fa-thermometer-half' style='color:#059e8a;'></i> ".$value." °C</h2></center>"; break; case "humidityOUT": echo "<center><h1>Wilgotność na zewnÄ…trz</h1><h2><i class='fas fa-tint' style='color:#00add6;'></i> ".$value." %</h2></center>"; break; case "timestamp": echo "<center><h1>Data i godzina pomiaru</h1><h2><i class='fas fa-clock'></i> ".$value."</h2></center>"; break; } } } echo "</div>"; $conn -> close(); } } else { echo "Niepoprawny API-KEY"; } ?> <div id="chartContainer" style="height: 370px; width: 100%;"></div> <script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script> </body> </html> |
Creating charts using CanvasJS.com.
Next, connect the ESP8266 board to the computer and launch the Arduino IDE. You need to add support for the ESP8266 board before you can upload the software.
Once we have added the board to Arduino and uploaded the additional libraries for Wi-Fi and the DTH sensor, we can upload the software to the board.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
#include <ESP8266WiFi.h> // [biblioteka wbudowana] ESP8266 WiFi #include "DHT.h" #include "config.h" // plik konfiguracyjny do edycji (musi być w tym samym folderze co niniejszy plik) WiFiClient client; // WiFi connection #define DHTPIN_1 4 // what digital pin the DHT22 is conected to #define DHTTYPE_DHT22 DHT22 // there are multiple kinds of DHT sensors #define DHTPIN_2 0 #define DHTTYPE_DHT11 DHT11 DHT dht_temp_hum_in(DHTPIN_2, DHTTYPE_DHT11); DHT dht_temp_hum_out(DHTPIN_1, DHTTYPE_DHT22); void setup() { Serial.begin(115200); // initialize the serial port pinMode(LED_BUILTIN, OUTPUT); // set builtin LED for output while (!Serial) { } // Wait dht_temp_hum_out.begin(); dht_temp_hum_in.begin(); logonToRouter(); // logon to local Wi-Fi } void loop() { postToRPi(); // send data to RPi delay(300000); } void logonToRouter() { String exitMessage = ""; int count = 0; WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { count++; // give up if more than 15 tries if (count > 15) { // display error code on serial monitor switch (WiFi.status()) { case 1: exitMessage = "Sieć Wi-Fi niedostępna lub\nStacja zbyt daleko od punktu dostępowego lub\nNiepoprawny SSID lub hasło lub\nAccess Poin nie pracuje na częstotliwości 2.4GHz."; break; case 2: // will never show this condition exitMessage = "Skanowanie sieci zakończone."; break; case 3: // will never show this condition exitMessage = "Połączono."; break; case 4: exitMessage = "Błąd połączenia."; break; case 5: exitMessage = "Utracono połączenie."; break; case 6: exitMessage = "Rozłączono."; break; } // switch Serial.print("WiFi fail: "); Serial.println(exitMessage); // retry after 1 minute } // if > 15 // otherwise if < 15 blink LED and wait 500ms before checking connection delay(500); // one-half second delay between checks Serial.print(""); } // while not connected // WiFi is sucesfully connected Serial.println(""); // new line to show IP address Serial.print("Połączono z siecią Wi-Fi. Otrzymany adres IP: "); Serial.println(WiFi.localIP().toString()); // is toString necessary? } // logonToRouter() void postToRPi() { // assemble and post the data if (client.connect(IOT_SERVER, IOT_SERVER_PORT) == true) { // Sensor readings float hOUT = dht_temp_hum_out.readHumidity(); float tOUT = dht_temp_hum_out.readTemperature(); float hIN = dht_temp_hum_in.readHumidity(); float tIN = dht_temp_hum_in.readTemperature(); Serial.println("Połączono z serwerem RPi."); // get the data to RPi client.print("GET /espdata.php?"); client.print("api_key="); client.print(write_api_key); client.print("&&"); client.print("station_id="); client.print(table_name); client.print("&&"); client.print("tIN="); client.print(tIN); client.print("&&"); client.print("hIN="); client.print(hIN); client.print("&&"); client.print("tOUT="); client.print(tOUT); client.print("&&"); client.print("hOUT="); client.print(hOUT); client.println(" HTTP/1.1"); client.println("Host: localhost"); client.println("Content-Type: application/x-www-form-urlencoded"); client.println("Connection: close"); client.println(); client.println(); Serial.println("Wysłano dane na serwer RPi."); } client.stop(); Serial.println("Rozłączono z serwerem RPi."); } |
It is important to ensure that the correct pins to which the sensors are soldered are specified in the code.
Rainmeter
The widget for Windows 10 was implemented using Rainmeter, for which a plugin was developed.
The plugin code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
[Metadata] Name=Stacja Meteo Author=Artur Kos Information=Email: R_E_D_O_X@wp.pl License=Free For All Version=1.0.0 [StacjaMeteo] Update=300000 Measure=WebParser URL=https://artur-kos.tplinkdns.com/readmeteo.json RegExp=(?siU)"time": "(.*)".*tIN": "(.*)".*hIN": "(.*)".*tOUT": "(.*)".*hOUT": "(.*)" [DateTime] Measure=WebParser URL=[StacjaMeteo] StringIndex=1 [Temperature_IN] Measure=WebParser URL=[StacjaMeteo] StringIndex=2 [Humidity_IN] Measure=WebParser URL=[StacjaMeteo] StringIndex=3 [Temperature_OUT] Measure=WebParser URL=[StacjaMeteo] StringIndex=4 [Humidity_OUT] Measure=WebParser URL=[StacjaMeteo] StringIndex=5 [MeterBackground] Meter=Image W=380 H=135 SolidColor=20,20,20,255 [DateTimeLabel] Meter=String X=5 Y=5 W=360 H=15 FontSize=11 FontColor=255,225,181,255 SolidColor=47,47,47,255 Padding=5,5,5,5 AntiAlias=1 Text=Data i godzina pomiaru: [MeterDateTime] Meter=String MeasureName=DateTime X=370 Y=5 W=300 H=15 FontSize=11 FontColor=252,251,202,255 SolidColor=0,0,0,1 Padding=5,5,5,5 StringAlign=Right AntiAlias=1 [TemperatureINLabel] Meter=String X=5 Y=30 W=360 H=15 FontSize=11 FontColor=255,225,181,255 SolidColor=47,47,47,255 Padding=5,5,5,5 AntiAlias=1 Text=Temperatura w domu (stopnie Celsjusza): [MeterTemperatureIN] Meter=String MeasureName=Temperature_IN X=370 Y=30 W=300 H=15 FontSize=11 FontColor=252,251,202,255 SolidColor=0,0,0,1 Padding=5,5,5,5 StringAlign=Right AntiAlias=1 [HumidityINLabel] Meter=String X=5 Y=55 W=360 H=15 FontSize=11 FontColor=255,225,181,255 SolidColor=47,47,47,255 Padding=5,5,5,5 AntiAlias=1 Text=Wilgotnosc w domu (%): [MeterHumidityIN] Meter=String MeasureName=Humidity_IN X=370 Y=55 W=300 H=15 FontSize=11 FontColor=252,251,202,255 SolidColor=0,0,0,1 Padding=5,5,5,5 StringAlign=Right AntiAlias=1 [TemperatureOUTLabel] Meter=String X=5 Y=80 W=360 H=15 FontSize=11 FontColor=255,225,181,255 SolidColor=47,47,47,255 Padding=5,5,5,5 AntiAlias=1 Text=Temperatura na zewnatrz (stopnie Celsjusza): [MeterTemperatureOUT] Meter=String MeasureName=Temperature_OUT X=370 Y=80 W=350 H=15 FontSize=11 FontColor=252,251,202,255 SolidColor=0,0,0,1 Padding=5,5,5,5 StringAlign=Right AntiAlias=1 [HumidityOUTLabel] Meter=String X=5 Y=105 W=360 H=15 FontSize=11 FontColor=255,225,181,255 SolidColor=47,47,47,255 Padding=5,5,5,5 AntiAlias=1 Text=Wilgotnosc na zewnatrz (%): [MeterHumidityOUT] Meter=String MeasureName=Humidity_OUT X=370 Y=105 W=300 H=15 FontSize=11 FontColor=252,251,202,255 SolidColor=0,0,0,1 Padding=5,5,5,5 StringAlign=Right AntiAlias=1 |