Arduino® UNO Q - Local Weather Station
Build a fully autonomous weather station that collects environmental data, stores it locally, and visualizes it in real time on an interactive web dashboard: no cloud, no external servers, full control over your data.
Components and supplies
Gravity: Analog Ambient Light Sensor TEMT6000
Touch Screen Display 7 inch IPS LCD
Pressure Sensor BMP 280
GUVA-S12SD UV sensor
Nicla Sense Env
Rain Drop sensor
USB-C to HDMI multiport adapter 4K, USB hub, PD pass through
Arduino UNO Q
40 colored male-female jumper wires
Modulino® Thermo
Tools and machines
soldering iron
Apps and platforms
Arduino IDE 2.0 (beta)
Arduino Lab for MicroPython
Project description
Code
main.py
python
main.py file for the project
1# SPDX-FileCopyrightText: Copyright (C) 2025 ARDUINO SA <http://www.arduino.cc> 2# 3# SPDX-License-Identifier: MPL-2.0 4 5import datetime 6import math 7import random 8import threading 9import time 10from arduino.app_bricks.dbstorage_tsstore import TimeSeriesStore 11from arduino.app_bricks.web_ui import WebUI 12from arduino.app_utils import App, Bridge 13 14db = TimeSeriesStore() 15USE_TEST_DATA = False # Set to True to generate random test data instead of using real sensor data 16 17def on_get_samples(resource: str, start: str, aggr_window: str): 18 samples = db.read_samples(measure=resource, start_from=start, aggr_window=aggr_window, aggr_func="mean", limit=180) # 3 hours of data at 1 sample/min 19 return [{"ts": s[1], "value": s[2]} for s in samples] 20 21ui = WebUI() 22ui.expose_api("GET", "/get_samples/{resource}/{start}/{aggr_window}", on_get_samples) 23 24def generate_startup_data(): 25 print("Generating startup data for the last 3 hours...") 26 now = datetime.datetime.now() 27 for i in range(180): # 3 hours * 60 minutes 28 ts = int((now - datetime.timedelta(minutes=i)).timestamp() * 1000) 29 30 celsius = round(random.uniform(15.0, 30.0), 2) 31 humidity = round(random.uniform(40.0, 90.0), 2) 32 pressure = round(random.uniform(980.0, 1030.0), 2) 33 lux = round(random.uniform(0.0, 20000.0), 2) 34 raindrop = random.randint(0, 1) 35 uv_index = round(random.uniform(0.0, 10.0), 2) 36 tvoc = round(random.uniform(0.0, 1000.0), 2) 37 eco2 = round(random.uniform(400.0, 2000.0), 2) 38 39 db.write_sample("temperature", float(celsius), ts) 40 db.write_sample("humidity", float(humidity), ts) 41 db.write_sample("pressure", float(pressure), ts) 42 db.write_sample("lux", float(lux), ts) 43 db.write_sample("raindrop", int(raindrop), ts) 44 db.write_sample("uv_index", float(uv_index), ts) 45 db.write_sample("tvoc", float(tvoc), ts) 46 db.write_sample("eco2", float(eco2), ts) 47 print("Startup data generation complete.") 48 49last_update_time = 0 50 51def record_sensor_samples(celsius: float, humidity: float, pressure: float, lux: float, raindrop: int, uv_index: float, tvoc: float, eco2: float): 52 """Callback invoked by the board sketch via Bridge.notify to send sensor samples. 53 Stores temperature and humidity samples in the time-series DB and forwards them to the Web UI. 54 """ 55 global last_update_time 56 current_time = time.time() 57 if current_time - last_update_time < 10: 58 return 59 last_update_time = current_time 60 61 if celsius is None or humidity is None: 62 print("Received invalid sensor samples: celsius=%s, humidity=%s" % (celsius, humidity)) 63 return 64 65 if USE_TEST_DATA: 66 celsius = round(random.uniform(15.0, 30.0), 2) 67 humidity = round(random.uniform(40.0, 90.0), 2) 68 pressure = round(random.uniform(980.0, 1030.0), 2) 69 lux = round(random.uniform(0.0, 20000.0), 2) 70 raindrop = random.randint(0, 1) 71 uv_index = round(random.uniform(0.0, 10.0), 2) 72 tvoc = round(random.uniform(0.0, 1000.0), 2) 73 eco2 = round(random.uniform(400.0, 2000.0), 2) 74 75 ts = int(datetime.datetime.now().timestamp() * 1000) 76 # Write samples to time-series DB 77 db.write_sample("temperature", float(celsius), ts) 78 db.write_sample("humidity", float(humidity), ts) 79 db.write_sample("pressure", float(pressure), ts) 80 db.write_sample("lux", float(lux), ts) 81 db.write_sample("raindrop", int(raindrop), ts) 82 db.write_sample("uv_index", float(uv_index), ts) 83 db.write_sample("tvoc", float(tvoc), ts) 84 db.write_sample("eco2", float(eco2), ts) 85 86 # Push realtime updates to the UI 87 ui.send_message('temperature', {"value": float(celsius), "ts": ts}) 88 ui.send_message('humidity', {"value": float(humidity), "ts": ts}) 89 ui.send_message('pressure', {"value": float(pressure), "ts": ts}) 90 ui.send_message('lux', {"value": float(lux), "ts": ts}) 91 ui.send_message('raindrop', {"value": int(raindrop), "ts": ts}) 92 ui.send_message('uv_index', {"value": float(uv_index), "ts": ts}) 93 ui.send_message('tvoc', {"value": float(tvoc), "ts": ts}) 94 ui.send_message('eco2', {"value": float(eco2), "ts": ts}) 95 96 97print("Registering 'record_sensor_samples' callback.") 98Bridge.provide("record_sensor_samples", record_sensor_samples) 99 100App.start_brick(db) 101 102# Generate historical data for the last 3 hours on startup 103if USE_TEST_DATA: 104 generate_startup_data() 105 106print("Starting App...") 107App.run()
.ino sketch
c
Main sketch
1#include <Modulino.h> 2#include <Arduino_RouterBridge.h> 3#include "Arduino_NiclaSenseEnv.h" 4#include <Adafruit_BMP280.h> 5 6// Create object instance 7ModulinoThermo thermo; 8Adafruit_BMP280 bmp(&Wire); 9 10#define RAIN_SENSOR_PIN D2 11#define UV_SENSOR_PIN A0 12#define AMBIENT_LIGHT_SENSOR_PIN A1 13 14unsigned long previousMillis = 0; // Stores last time values were updated 15const long interval = 1000; // Every second 16 17bool sgp_initialised = false; 18bool bmp_initialised = false; 19 20NiclaSenseEnv device; 21 22void setup() { 23 Bridge.begin(); 24 Monitor.begin(); 25 26 // Initialize Modulino I2C communication 27 Modulino.begin(Wire1); 28 29 // Initialize sensors 30 thermo.begin(); 31 pinMode(RAIN_SENSOR_PIN, INPUT); // Rain sensor pin 32 33 if (device.begin()) { 34 Monitor.println("Nicla Sense Env - Init OK"); 35 IndoorAirQualitySensor indoorAirQualitySensor = device.indoorAirQualitySensor(); 36 indoorAirQualitySensor.setMode(IndoorAirQualitySensorMode::indoorAirQuality); 37 sgp_initialised = true; 38 } else { 39 Monitor.println("Nicla Sense Env - Init FAIL"); 40 sgp_initialised = false; 41 } 42 43 if (bmp.begin(0x76)) { 44 Monitor.println("BMP280 - Init OK"); 45 bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ 46 Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */ 47 Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ 48 Adafruit_BMP280::FILTER_X16, /* Filtering. */ 49 Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */ 50 bmp_initialised = true; 51 } else { 52 Monitor.println("BMP280 - Init FAIL"); 53 bmp_initialised = false; 54 } 55} 56 57void loop() { 58 unsigned long currentMillis = millis(); // Get the current time 59 if (currentMillis - previousMillis >= interval) { 60 // Save the last time you updated the values 61 previousMillis = currentMillis; 62 63 // Read temperature in Celsius from the sensor 64 float celsius = thermo.getTemperature(); 65 66 // Read humidity percentage from the sensor 67 float humidity = thermo.getHumidity(); 68 69 // Read pressure from the sensor 70 float pressure = -255; 71 if (bmp_initialised) { 72 pressure = bmp.readPressure() / 100; // Pa -> hPA 73 } 74 75 // Read raw light level from the sensor and convert in lux 76 int light_raw = analogRead(AMBIENT_LIGHT_SENSOR_PIN); 77 float light_volt = light_raw * (5.0 / 1023.0); 78 float lux = light_volt * 200; 79 80 // Read rain sensor value 81 int raindrop = (digitalRead(RAIN_SENSOR_PIN) == HIGH) ? 0 : 1; 82 83 // Read UV sensor value 84 int uv_raw = analogRead(UV_SENSOR_PIN); 85 float uv_volt = uv_raw * (5.0 / 1023); 86 float uv_index = uv_volt * 10.0; 87 88 // Read air quality from SGP30 sensor 89 float tvoc = -255; 90 float eco2 = -255; 91 92 if (sgp_initialised) { 93 if (device.indoorAirQualitySensor().enabled()) { 94 auto iaqMode = device.indoorAirQualitySensor().mode(); 95 96 if (iaqMode == IndoorAirQualitySensorMode::indoorAirQuality || iaqMode == IndoorAirQualitySensorMode::indoorAirQualityLowPower) { 97 tvoc = device.indoorAirQualitySensor().TVOC(); 98 eco2 = device.indoorAirQualitySensor().CO2(); 99 } 100 } 101 } 102 103 Monitor.print("Temperature (°C): " + String(celsius)); 104 Monitor.print(" Humidity (%): " + String(humidity)); 105 Monitor.print(" Pressure (hPA): " + String(pressure)); 106 Monitor.print(" Light (lux): " + String(lux)); 107 Monitor.print(" Rain detected (yes-1,no-0): " + String(raindrop)); 108 Monitor.print(" UV index: " + String(uv_index)); 109 Monitor.print(" TVOC (ppb): " + String(tvoc)); 110 Monitor.println(" eCO2 (ppm): " + String(eco2)); 111 112 Bridge.notify("record_sensor_samples", celsius, humidity, pressure, lux, raindrop, uv_index, tvoc, eco2); 113 } 114}
Github repo with full project code
Go here to explore the full code and assets!
Downloadable files
Schematic for the Project
Sensor connection schematic
Schematic.png

Hub connection
Hub connection for the project
Connection.png

Comments
Only logged in users can leave comments