A cookbook for a weather station — Part II

Collecting and archiving measurement data

In part I of my weather station cookbook, I wrote about setting up and publishing sensor data to MQTT using an Arduino. In this post, I’ll focus on storing that data so it can be analyzed over time.

MQTT only keeps the latest value, so to create a proper history, I decided to save everything in a database — in SQLite because it’s file-based — you can open it directly, inspect the data, or even move it like any other file.

A tidbit diverging from the name of this blog, I implemented the logger in Rust instead of Python.

The script itself is pretty simple:

  • it sets up the SQLite database and ensures the measurements table exists:
rust
use rusqlite::Connection;

...

#[tokio::main]
async fn main() -> Result<(), Box> { 
    let conn = Connection::open("weather_station.db").expect("Failed to open DB");
        conn.execute(
            "CREATE TABLE IF NOT EXISTS measurements (
                timestamp TEXT NOT NULL,
                value REAL NOT NULL,
                metric TEXT NOT NULL,
                unit TEXT NOT NULL
            )",
            [],
        )?;   

    ...

}
  • it configures and connects the MQTT client to the broker and subscribes to the temperature and humidity topics:
rust
use rumqttc::{MqttOptions, Client, QoS};
use std::time::Duration;

...

#[tokio::main]
async fn main() -> Result<(), Box> {

    let mut mqttoptions = MqttOptions::new("mqtt_logger", "localhost", 1883);

    mqttoptions.set_keep_alive(Duration::from_secs(15 * 60));

    mqttoptions.set_clean_session(false);

    let (client, mut eventloop) = Client::new(mqttoptions, 10);
    client.subscribe("weather/indoor-sensor/temperature", QoS::AtMostOnce)?;
    client.subscribe("weather/indoor-sensor/humidity", QoS::AtMostOnce)?;

    ...

}
  • it continuously listens for incoming messages in a dedicated thread, which parses each message from json and inserts it into the database - errors in parsing or database operations are logged, and the MQTT connection automatically retries on failure:
rust
use rusqlite::params;
use serde::Deserialize;
use serde_json;
use std::thread;

#[derive(Deserialize)]
struct SensorPayload {
    timestamp: i64,
    metric: String,
    value: f64,
    unit: String,
}

#[tokio::main]
async fn main() -> Result<(), Box> {
    ...

    let handle = thread::spawn(move || {
        for message in eventloop.iter() {
            match message {
                Ok(rumqttc::Event::Incoming(rumqttc::Packet::Publish(p))) => {
                    if let Ok(text) = String::from_utf8(p.payload.to_vec()) {
                        match serde_json::from_str::(&text) {
                        Ok(data) => {
                            if let Err(e) = conn.execute(
                                    "INSERT INTO measurements
                                        (timestamp, value, metric, unit)
                                    VALUES (?1, ?2, ?3, ?4)",
                                    params![
                                        data.timestamp,
                                        data.value,
                                        data.metric,
                                        data.unit
                                    ],
                                )  {
                                    eprintln!("DB insert error: {}", e);
                                } else {
                                    println!("Inserted @ {}", data.timestamp);
                                }
                            }
                            Err(e) => eprintln!("JSON parse error: {}", e),
                        }
                    }
                }
                Ok(_) => {}
                Err(e) => {
                    eprintln!("MQTT error: {} — reconnecting in 5s", e);
                    std::thread::sleep(std::time::Duration::from_secs(5));
                }
            }
        }
    });

    ...

}
  • the main thread is blocked until the MQTT thread finishes - in practice it runs forever:
rust

...

#[tokio::main]
async fn main() -> Result<(), Box> {
    ...

    let handle = ... ;

    handle.join().unwrap();

    Ok(())
}

Looking ahead

With the logger in place, I now have a reliable stream of weather data available for further use.

In the next part of this series, I’ll finally move on to some exploratory data analysis — this time with Python 💙

I also want to try out Marimo Notebooks, which I’ve heard promising things about, and see how they compare to similar tools like Jupyter.

My plan is to dig into the collected data, visualize some first trends, and start answering interesting questions about what the weather station has been recording:

  • What patterns will show up in the data?
  • How do temperature and humidity change throughout the day?
  • Are there any surprising correlations hidden in the measurements?

I’m excited to find out more — see you in part III!

Once again, if you find this project interesting, you can check it out in my weather-station repo.


A cookbook for a weather station — Part I

Have you ever wondered how to measure temperature and humidity?

I decided to explore it by building my own setup from sensor to display:

flowchart TD ESP["🔌 NodeMCU ESP8266"] -->|Digital Pin| DHT["🌡️ DHT22 Sensor"] ESP -->|Wi-Fi / MQTT Publish| MQTT["📡 MQTT Broker"] subgraph "🍓 Raspberry Pi" direction TB MQTT Rust["🦀 Rust Logger"] DB["🗄️ SQLite Database"] HA["🏠 Home Assistant"] end Rust -->|Subscribes to topic| MQTT Rust -->|Writes data to| DB HA -->|Reads data from| MQTT
Hardware stack
NodeMCU ESP8266 acts as the "brain" of the weather station - reads data from the sensor and forwards it via Wi-Fi
DHT22 measures temperature and humidity
Raspberry Pi hosts an MQTT client that receives weather data published via the NodeMCU ESP8266 over Wi-Fi, allowing the data to be stored, processed, and shared
Software stack
MQTT messaging protocol for IoT using a publish/subscribe system
Arduino / C++ NodeMCU ESP8266 runs Arduino/C++ code to read data from the sensor and publish to the MQTT topics
Home Assistant subscribes to the MQTT topics to receive and display the measurements
SQLite database to archive and store the weather station’s historical data on the Raspberry Pi
Rust implements a logger that saves incoming MQTT weather data into the SQLite database

Assembling the sensor

To connect a DHT22 temperature and humidity sensor to the NodeMCU ESP8266, wire the VCC pin of the DHT22 to the NodeMCU ESP8266’s 3.3V pin and the GND pin to a GND pin on the NodeMCU ESP8266. Then connect the Data pin to the digital pin (D2) on the NodeMCU ESP8266.

If you want a visual walkthrough, Arduino provides an official tutorial that covers this step-by-step.

Retrieving temperature and humidity values

The NodeMCU ESP8266 and DHT22 sensor work together: every 10 minutes, the device connects to Wi-Fi and syncs its clock with an NTP server to get the correct time:

cpp
#include <WiFiUdp.h>
#include <NTPClient.h>

...

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, 60000);

...

timeClient.update();

The DHT22 library handles updating temperature and humidity values:

cpp
#include <DHT22.h>

...

#define pinDATA SDA

...

DHT22 dht22(pinDATA);

...

float humidity = dht22.getHumidity();
float temperature = dht22.getTemperature();

Publishing data to MQTT

Since NodeMCU ESP8266 has a Wi-Fi module, the measured values can be published to an MQTT client hosted on a Raspberry Pi.

The board first joins the Wi-Fi network:

cpp
#include <ESP8266WiFi.h>

...

WiFiClient espClient;

...

const char* ssid = "ssid";
const char* password = "password";

...

void setup_wifi() {
  delay(10);
  Serial.println("Connecting to WiFi ...");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected. IP: ");
  Serial.println(WiFi.localIP());
}

With the Wi-Fi connection available, the board can publish data to MQTT:

cpp
#include <PubSubClient.h>


...

PubSubClient client(espClient);

...

const int mqtt_port = 1883;
const char* mqtt_server = "mqtt_server";

...

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection ...");
    if (client.connect("NodeMCUClient")) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" retrying in 5 seconds");
      delay(5000);
    }
  }
}

...

void setup() {
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  timeClient.begin();
}

...

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  timeClient.update();

  ...

}

Sensor measurements are sent as json messages containing the metric, value, unit, timestamp, and published via MQTT to designated topics. Since the NodeMCU ESP8266 transmits every 10 minutes, the buffer time is sufficient to capture all measurements.

cpp
const char* mqtt_temperature_topic = "weather/indoor-sensor/temperature";
const char* mqtt_humidity_topic = "weather/indoor-sensor/humidity";

...

void loop() {
  // Check if readings are valid
  if (!isnan(humidity) && !isnan(temperature)) {
    unsigned long timestamp = timeClient.getEpochTime();

    // Prepare temperature payload as JSON
    String temperature_payload = "{\"metric\": \"temperature\", \"value\": " + String(temperature) + ", \"unit\": \"℃\", \"timestamp\": " + String((uint64_t)timestamp) + "}";

    // Prepare humidity payload as JSON
    String humidity_payload = "{\"metric\": \"humidity\", \"value\": " + String(humidity) + ", \"unit\": \"%\", \"timestamp\": " + String((uint64_t)timestamp) + "}";

    // Publish temperature
    client.publish(mqtt_temperature_topic, temperature_payload.c_str());
    Serial.println("Published temperature: " + temperature_payload);

    // Publish humidity
    client.publish(mqtt_humidity_topic, humidity_payload.c_str());
    Serial.println("Published humidity: " + humidity_payload);

  } else {
    Serial.println("Failed to read from DHT sensor");
  }

  delay(600000);

}

Syncing with Home Assistant

Once the data lands in MQTT, the measurements can be displayed in Home Assistant configuring MQTT Sensor.

Up next

In the next part, I’ll dive into my Rust-based logger and share how it works.

Stay tuned, Pyrsuers! 🐍

If you would like to check out the entire project, it is available in my weather-station repo.