Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
|
Project Overview
This project transforms the compact **M5StickC PLUS 2** with an **ENV III sensor module** into a real-time environmental monitoring station that displays temperature, humidity, and air pressure — and even predicts the weather based on sensor data.
---
Why This Project?
Weather affects our lives every day, but having a personal weather station can be expensive or too bulky for daily use.
This lightweight, low-power solution fits in your hand, displays real-time readings on screen, and pushes data to a web dashboard for visualization and prediction.
It’s an excellent entry point for:
- Learning embedded systems and Python
- Practicing data visualization with Streamlit
- Exploring weather prediction from a data science perspective
- --
How It Works
1. **Sensor Collection**
The M5StickC PLUS 2 reads temperature (°C), humidity (%), and air pressure (hPa) from the ENV III sensor every 2 seconds.
2. **Serial Communication**
The data is printed via serial in CSV format and received on a host computer using Python (`serial_to_csv.py`).
3. **Data Logging and Visualization**
A Python Streamlit web dashboard (`app.py`) loads the CSV file and plots time-series trends for all three environmental variables with prediction logic.
4. **Weather Forecasting**
Based on recent changes in pressure, temperature, and humidity, the system predicts possible weather conditions (e.g., sunny, rainy, or stable).
---
What Makes It Special?
M5StickC PLUS 2 Environmental Monitor - Arduino Code
C/C++#include <Wire.h>
#include <M5StickCPlus2.h>
#include <Adafruit_SHT31.h>
#include <DFRobot_QMP6988.h>
//
Adafruit_SHT31 sht30 = Adafruit_SHT31();
DFRobot_QMP6988 qmp;
void setup() {
M5.begin();
Wire.begin(); // SDA/SCL
Serial.begin(115200);
delay(1000);
Serial.println("Initializing sensors...");
//
if (!sht30.begin(0x44)) {
Serial.println("Couldn't find SHT30 sensor!");
while (1);
}
//
if (!qmp.begin()) {
Serial.println("Couldn't find QMP6988 sensor!");
while (1);
}
M5.Lcd.setRotation(3);
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
}
void loop() {
float temperature = sht30.readTemperature(); //
float humidity = sht30.readHumidity(); //
float pressure = qmp.readPressure(); // Pa
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10, 10);
if (!isnan(temperature)) {
M5.Lcd.printf("Temp: %.2f C", temperature);
} else {
M5.Lcd.println("Temp error");
}
M5.Lcd.setCursor(10, 40);
if (!isnan(humidity)) {
M5.Lcd.printf("Humi: %.2f %%", humidity);
} else {
M5.Lcd.println("Humi error");
}
M5.Lcd.setCursor(10, 70);
if (!isnan(pressure)) {
M5.Lcd.printf("Pres: %.2f hPa", pressure / 100.0); // hPa
} else {
M5.Lcd.println("Pres error");
}
// serial_to_csv.py
Serial.printf("T: %.2f, H: %.2f, P: %.2f\n", temperature, humidity, pressure / 100.0);
delay(2000);
}
Serial to CSV Logger
Pythonimport serial
import pandas as pd
from datetime import datetime
#
port = 'COM5'
baudrate = 115200
ser = serial.Serial(port, baudrate, timeout=1)
data = []
print(" Receiving data...")
try:
while len(data) < 100: # 100
line = ser.readline().decode(errors='ignore').strip()
if "T:" in line and "H:" in line and "P:" in line:
try:
parts = line.split(",")
temp = float(parts[0].split(":")[1])
humi = float(parts[1].split(":")[1])
pres = float(parts[2].split(":")[1])
timestamp = datetime.now().strftime("%H:%M:%S")
data.append([timestamp, temp, humi, pres])
print(f"{timestamp} | Temp: {temp}C | Humi: {humi}% | Press: {pres}hPa")
except:
continue
finally:
ser.close()
df = pd.DataFrame(data, columns=["time", "temperature", "humidity", "pressure"])
df.to_csv("data.csv", index=False, encoding='utf-8')
print(" Data saved to data.csv")
Streamlit Dashboard for Weather Monitoring
Pythonimport streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates #
st.set_page_config(page_title="Environmental Monitoring Dashboard", layout="centered")
st.title(" M5StickC PLUS 2 Environmental Monitor")
# Load CSV data
df = pd.read_csv("data.csv")
df['time'] = pd.to_datetime(df['time']) # time datetime
# Show latest data preview
st.subheader(" Latest Data Preview")
st.dataframe(df.tail(10))
# Temperature trend
st.subheader(" Temperature Trend")
fig1, ax1 = plt.subplots(figsize=(6, 3))
ax1.plot(df['time'], df['temperature'], color='orange', label='Temperature (C)')
ax1.set_xlabel("Time")
ax1.set_ylabel("Temperature (C)")
ax1.xaxis.set_major_locator(mdates.AutoDateLocator()) #
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) #
ax1.tick_params(axis='x', rotation=45)
ax1.legend()
st.pyplot(fig1)
# Humidity trend
st.subheader(" Humidity Trend")
fig2, ax2 = plt.subplots(figsize=(6, 3))
ax2.plot(df['time'], df['humidity'], color='blue', label='Humidity (%)')
ax2.set_xlabel("Time")
ax2.set_ylabel("Humidity (%)")
ax2.xaxis.set_major_locator(mdates.AutoDateLocator())
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
ax2.tick_params(axis='x', rotation=45)
ax2.legend()
st.pyplot(fig2)
# Pressure trend
st.subheader(" Pressure Trend")
fig3, ax3 = plt.subplots(figsize=(6, 3))
ax3.plot(df['time'], df['pressure'], color='green', label='Pressure (hPa)')
ax3.set_xlabel("Time")
ax3.set_ylabel("Pressure (hPa)")
ax3.xaxis.set_major_locator(mdates.AutoDateLocator())
ax3.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
ax3.tick_params(axis='x', rotation=45)
ax3.legend()
st.pyplot(fig3)
# Weather prediction
st.subheader(" Weather Forecast")
if len(df) >= 2:
#
t_now, h_now, p_now = df[['temperature', 'humidity', 'pressure']].iloc[-1]
t_prev, h_prev, p_prev = df[['temperature', 'humidity', 'pressure']].iloc[-2]
dt = t_now - t_prev
dh = h_now - h_prev
dp = p_now - p_prev
#
st.markdown(f"**Pressure :** `{dp:.2f} hPa`**Humidity :** `{dh:.2f} %`**Temp :** `{dt:.2f} C`")
#
if dp < -1.0 and dh > 1.0 and dt < -0.3:
forecast = " Likely rain or overcast pressure dropping, humidity rising"
elif dp > 1.0 and dh < -1.0 and dt > 0.3:
forecast = " Clear or improving pressure rising and drier"
elif abs(dp) < 0.5 and abs(dh) < 2 and abs(dt) < 0.5:
forecast = " Stable weather no major changes"
else:
forecast = " Fluctuating unclear, possibly humid or cloudy"
st.markdown(f"**Forecast:** {forecast}")
else:
st.write("Not enough data to predict weather.")
st.markdown("""
---
This dashboard visualizes real-time environmental data (temperature, humidity, and pressure) collected by the M5StickC PLUS 2 + ENV III module.
Data is sampled every 2 seconds and can support basic weather trend predictions.
""")
Sample Data File (data.csv)
Textiletime,temperature,humidity,pressure
18:38:25,24.69,69.83,10.13
18:38:27,24.69,69.71,10.13
18:38:29,24.69,69.61,10.13
18:38:31,24.7,69.86,10.13
18:38:33,24.73,69.99,10.13
18:38:35,24.73,70.14,10.13
18:38:37,24.77,70.09,10.13
18:38:39,24.74,69.96,10.13
18:38:41,24.74,69.92,10.13
18:38:43,24.76,69.88,10.13
18:38:45,24.76,69.81,10.13
18:38:47,24.79,69.74,10.13
18:38:49,24.77,69.7,10.13
18:38:51,24.77,69.66,10.13
18:38:54,24.79,69.62,10.13
18:38:56,24.79,69.57,10.13
18:38:58,24.79,69.57,10.13
18:39:00,24.83,69.56,10.13
18:39:02,24.81,69.63,10.13
18:39:04,24.8,69.76,10.13
18:39:06,24.8,69.86,10.13
18:39:08,24.83,69.94,10.13
18:39:10,24.81,69.94,10.13
18:39:12,24.84,69.96,10.13
18:39:14,24.83,69.92,10.13
18:39:16,24.81,69.9,10.13
18:39:18,24.83,69.96,10.13
18:39:20,24.84,70.21,10.13
18:39:22,24.86,70.41,10.13
18:39:25,24.83,70.51,10.13
18:39:27,24.87,70.65,10.13
18:39:29,24.84,70.74,10.13
18:39:31,24.86,70.72,10.13
18:39:33,24.87,70.89,10.13
18:39:35,24.87,71.08,10.13
18:39:37,24.87,71.24,10.13
18:39:39,24.88,71.33,10.13
18:39:41,24.87,71.51,10.13
18:39:43,24.87,71.67,10.13
18:39:45,24.88,71.86,10.13
18:39:47,24.87,71.98,10.13
18:39:49,24.91,72.07,10.13
18:39:51,24.88,72.12,10.13
18:39:53,24.9,72.2,10.13
18:39:56,24.88,72.35,10.13
18:39:58,24.9,72.45,10.13
18:40:00,24.91,72.52,10.13
18:40:02,24.9,72.55,10.13
18:40:04,24.9,72.58,10.13
18:40:06,24.9,72.6,10.13
18:40:08,24.91,72.58,10.13
18:40:10,24.92,72.57,10.13
18:40:12,24.91,72.53,10.13
18:40:14,24.91,72.39,10.13
18:40:16,24.91,72.19,10.13
18:40:18,24.92,72.05,10.13
18:40:20,24.91,71.95,10.13
18:40:22,24.92,71.81,10.13
18:40:24,24.95,71.74,10.13
18:40:26,24.92,71.63,10.13
18:40:29,24.92,71.61,10.13
18:40:31,24.92,71.59,10.13
18:40:33,24.92,71.57,10.13
18:40:35,24.95,71.68,10.13
18:40:37,24.92,71.85,10.13
18:40:39,24.97,71.96,10.13
18:40:41,24.97,72.11,10.13
18:40:43,24.95,72.34,10.13
18:40:45,24.97,72.52,10.13
18:40:47,24.95,72.62,10.13
18:40:49,24.95,72.65,10.13
18:40:51,24.97,72.68,10.13
18:40:53,24.97,72.74,10.13
18:40:55,24.95,72.86,10.13
18:40:57,24.97,72.89,10.13
18:41:00,24.98,72.95,10.13
18:41:02,24.95,72.98,10.13
18:41:04,24.97,73.01,10.13
18:41:06,24.97,72.97,10.13
18:41:08,24.97,72.95,10.13
18:41:10,24.97,72.93,10.13
18:41:12,24.98,72.94,10.13
18:41:14,24.99,72.92,10.13
18:41:16,24.99,72.91,10.13
18:41:18,24.98,72.88,10.13
18:41:20,24.98,72.81,10.13
18:41:22,24.97,72.78,10.13
18:41:24,24.98,72.68,10.13
18:41:26,24.98,72.58,10.13
18:41:28,24.99,72.54,10.13
18:41:31,24.98,72.45,10.13
18:41:33,24.99,72.44,10.13
18:41:35,24.98,72.36,10.13
18:41:37,24.97,72.29,10.13
18:41:39,25.01,72.27,10.13
18:41:41,24.99,72.27,10.13
18:41:43,24.99,72.31,10.13
18:41:45,24.99,72.33,10.13
18:41:47,25.01,72.32,10.13
18:41:49,25.01,72.32,10.13
Comments