It's time to start collecting some data. This project uses the SQLAlchemy Python library to conveniently and efficiently store data in a SQLite database. Once data is collected in the database it can be queried from specified rooms, time ranges, or sensor types and exported into various formats. That makes it easy to experiment with how much, and what formats of data, are given to the LLMs.
Install SQLAlchemy
Activate the same virtual environment created in the steps on the Pi & Blinka Setup page if it is not already active.
Next, install SQLAlchemy with pip using this command.
source ~/venvs/sensor_llm_venv/bin/activate pip install SQLAlchemy
Database & Table Setup
The database must be initialized and have its table defined before it can start accepting records to store.
To do that with SQLAlchemy requires writing something called a "Model" class, which is just a way of listing out what fields will be stored and the types of data that they will contain.
The following script defines a SensorReading model to store the data values that will be read from a SEN6x sensor and when run from the terminal it ill initialize a database and table from the SensorReading class. If you're using a different sensor you may need to add, remove, or tweak fields to suite the data from your sensor.
When the model is set as needed run the script with this command to initialize the database.
python db_models.py
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
from datetime import datetime
from sqlalchemy import Column, Integer, Float, String, DateTime, create_engine
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class SensorReading(Base):
"""
Database model for environmental sensor readings.
"""
__tablename__ = "sensor_readings"
id = Column(Integer, primary_key=True, autoincrement=True)
datetime = Column(DateTime, nullable=False, default=datetime.utcnow)
room_name = Column(String(100), nullable=False)
temperature_c = Column(Float, nullable=True)
temperature_f = Column(Float, nullable=True)
humidity = Column(Float, nullable=True) # Percentage
pm25 = Column(Float, nullable=True) # PM2.5 in µg/m³
voc_index = Column(Float, nullable=True) # VOC index
nox_index = Column(Float, nullable=True) # NOx index
co2 = Column(Float, nullable=True) # CO2 in ppm
def __repr__(self):
return (
f"<SensorReading(room='{self.room_name}', "
f"datetime='{self.datetime}', "
f"temp_c={self.temperature_c}, "
f"humidity={self.humidity})>"
)
if __name__ == "__main__":
def create_database(db_url="sqlite:///sensor_data.db"):
"""Create the database and all tables."""
engine = create_engine(db_url, echo=True)
# Create all tables
Base.metadata.create_all(engine)
print(f"Database created successfully at: {db_url}")
return engine
# Create the database when script is run directly
create_database()
It will output the SQL command used to create the table, and other logging information. If successful it will create the file sensor_data.db.
SQLAlchemy makes it easy to insert new records into the database. Simply get a session for the DB, then create an instance of the SensorReading class setting each field to a data value taken from a sensor, add() the record to the database, and then commit() the changes to save it into the database file.
The following script will initialize the DB session and SEN6x sensor and then run a loop that takes readings and stores them in the database once per minute by default. The INTERVAL variable can be changed in order to collect data at intervals other than 1 minute. The ROOM_NAME variable is a string that will be stored in database and associated with each reading. Change it to whatever room you plan to keep the sensor in.
Save the file as take_sensor_readings.py run it with the this command.
python take_sensor_readings.py
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import board
import adafruit_sen6x
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from db_models import SensorReading
# How many seconds in between taking readings
INTERVAL = 60 # seconds
ROOM_NAME = "basement"
# Initialize I2C
i2c = board.I2C()
# Create SEN66 instance
sensor = adafruit_sen6x.SEN66(i2c)
# Read sensor info
print(f"Product: {sensor.product_name}")
print(f"Serial: {sensor.serial_number}")
# Check device status
status = sensor.device_status
print(f"Device {status}")
# Optional: Configure sensor before starting
# sensor.temperature_offset(offset=-2.0, slot=0) # Apply -2°C offset
# sensor.voc_algorithm_tuning(index_offset=100) # Adjust VOC baseline
# print(sensor.voc_algorithm) # Print VOC baseline
# CO2 configuration examples:
# sensor.co2_automatic_self_calibration = False # Disable ASC for greenhouses
# sensor.ambient_pressure = 1020 # Set pressure in hPa
# sensor.sensor_altitude = 500 # Or set altitude in meters
# Start measurements
sensor.start_measurement()
# Wait for first measurement to be ready
print("Waiting for first measurement...")
time.sleep(2)
print("-" * 40)
# connect to database
engine = create_engine("sqlite:///sensor_data.db")
Session = sessionmaker(bind=engine)
session = Session()
# Read data continuously
while True:
if sensor.data_ready:
# Check for errors before reading
sensor.check_sensor_errors()
# Read all measurements
data = sensor.all_measurements()
# create data object instance
reading = SensorReading(
room_name=ROOM_NAME,
temperature_c=data["temperature"],
temperature_f=(data["temperature"] * 9 / 5) + 32,
humidity=data["humidity"],
pm25=data["pm2_5"],
voc_index=data["voc_index"],
nox_index=data["nox_index"],
co2=data["co2"],
)
# Add and commit the reading to database
session.add(reading)
session.commit()
print(reading)
# Display values (None = sensor still initializing)
print(
f"Temperature: {data['temperature']:.1f}°C"
if data["temperature"]
else "Temperature: initializing..."
)
print(
f"Humidity: {data['humidity']:.1f}%"
if data["humidity"]
else "Humidity: initializing..."
)
print(
f"PM2.5: {data['pm2_5']:.1f} µg/m³"
if data["pm2_5"]
else "PM2.5: initializing..."
)
print(
f"VOC Index: {data['voc_index']:.1f}"
if data["voc_index"]
else "VOC Index: initializing..."
)
print(
f"NOx Index: {data['nox_index']:.1f}"
if data["nox_index"]
else "NOx Index: initializing..."
)
print(f"CO2: {data['co2']} ppm" if data["co2"] else "CO2: initializing...")
print("-" * 40)
time.sleep(INTERVAL)
Running in the Background
Running this script normally with python will result in it running in the foreground in the terminal that launched it. If the terminal is closed or the user logs out, the script will end and won't keep taking sensor readings.
If you want to keep this sensor running over a longer period of time, it is convenient to have it launch automatically and run in the background. That way it will run anytime the Pi is powered on, whether or not a user is actively logged in.
Linux has a utility called systemctl that is used to set up automated programs running in the background. It comes installed by default in Raspberry Pi OS. systemctl is configured by creating a .service file inside of /etc/systemd/system/.
This sensor-reading.service file is configured for the following:
- Linux username:
pi - Project code directory:
/home/pi/RaspberryPi_LLM_Sensor_Data/ - Python virtual environment located at:
/home/pi/venvs/sensor_llm_venv/
If any of these values differ for you, then you will need to update the relevant parts of the config file to include the proper username, project code directory, and python virtual environment locations for your setup.
Use nano to create the file, then paste the configuration into it.
sudo nano /etc/systemd/system/sensor-reading.service
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries # # SPDX-License-Identifier: MIT [Unit] Description=Python Script with Virtual Environment After=network.target Wants=network.target [Service] Type=simple User=pi Group=pi WorkingDirectory=/home/pi/RaspberryPi_LLM_Sensor_Data Environment=PATH=/home/pi/venvs/sensor_llm_venv/bin ExecStart=/home/pi/RaspberryPi_LLM_Sensor_Data/start_service.sh Restart=always RestartSec=10 # Optional: Set environment variables Environment=PYTHONPATH=/home/pi/RaspberryPi_LLM_Sensor_Data Environment=PYTHONUNBUFFERED=1 # Optional: Logging StandardOutput=journal StandardError=journal SyslogIdentifier=Environmental-Sensor-Reader # Optional: Security settings NoNewPrivileges=yes ProtectSystem=strict #ProtectHome=yes ReadWritePaths=/home/pi/RaspberryPi_LLM_Sensor_Data [Install] WantedBy=multi-user.target
It's easier to launch a shell script with systemctl then it is to launch a Python script inside of a specific virtual environment. The following start_service.sh file will move to the appropriate directory, activate the virtual environment, and launch the sensor reading python script.
Again, you will need to update the directories in this file if you have a different username, or stored the project or virtual environment in different locations.
#!/bin/bash # SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries # # SPDX-License-Identifier: MIT cd /home/pi/RaspberryPi_LLM_Sensor_Data source /home/pi/venvs/sensor_llm_venv/bin/activate exec python /home/pi/RaspberryPi_LLM_Sensor_Data/take_sensor_readings.py
Once the config and shell script are in place, enable and start the service with these commands:
sudo systemctl daemon-reload sudo systemctl enable sensor-reading.service sudo systemctl start sensor-reading.service
A tool called journalctl can be used to verify that the script is running successfully.
journalctl -e -u sensor-reading.service
This command will show output or error messages that come from the shell or Python scripts being launched by the service. If everything is working properly you'll see sensor readings being printed out.
Press the q key to exit.
Sep 22 08:42:31 raspberrypi your-service-name[779]: <SensorReading(room='RoomC', datetime='2025-09-22 13:42:31.173043', temp_c=20.925, humidity=65.98)> Sep 22 08:42:31 raspberrypi your-service-name[779]: Temperature: 20.9°C Sep 22 08:42:31 raspberrypi your-service-name[779]: Humidity: 66.0% Sep 22 08:42:31 raspberrypi your-service-name[779]: PM2.5: 3.8 µg/m³ Sep 22 08:42:31 raspberrypi your-service-name[779]: VOC Index: 31.0 Sep 22 08:42:31 raspberrypi your-service-name[779]: NOx Index: 1.0 Sep 22 08:42:31 raspberrypi your-service-name[779]: CO2: 422.0 ppm Sep 22 08:42:31 raspberrypi your-service-name[779]: ---------------------------------------- Sep 22 08:43:31 raspberrypi your-service-name[779]: <SensorReading(room='RoomC', datetime='2025-09-22 13:43:31.309236', temp_c=20.925, humidity=65.98)> Sep 22 08:43:31 raspberrypi your-service-name[779]: Temperature: 20.9°C Sep 22 08:43:31 raspberrypi your-service-name[779]: Humidity: 66.0% Sep 22 08:43:31 raspberrypi your-service-name[779]: PM2.5: 3.8 µg/m³ Sep 22 08:43:31 raspberrypi your-service-name[779]: VOC Index: 31.0 Sep 22 08:43:31 raspberrypi your-service-name[779]: NOx Index: 1.0 Sep 22 08:43:31 raspberrypi your-service-name[779]: CO2: 421.0 ppm
Page last edited September 23, 2025
Text editor powered by tinymce.