Getting Started with AtmosPyre
This guide will walk you through installing AtmosPyre and creating sensor logging applications, from basic setup to advanced multi-sensor monitoring.
Unfortunately, setup is both the first and hardest part. Make sure you understand the basics of RS485 Modbus.
Once the setup is finished, reading and logging is hopefully simple.
Installation
From PyPI
Recommended installation
For most users, install with the recommended backends:
This includes minimalmodbus for Modbus communication and schedule for scheduling.
Minimal installation
Install only the core library (you'll need to install backends separately):
Specific backends
Choose specific backends based on your needs:
# Just Modbus backend
pip install atmospyre[minimalmodbus]
# Just scheduling backend
pip install atmospyre[schedule]
# Combine multiple backends
pip install atmospyre[minimalmodbus,schedule]
# All available backends
pip install atmospyre[all]
From source (cloned repository)
If you've cloned the repository:
# Clone the repository
git clone https://git.iws.uni-stuttgart.de/measurements/atmospyre.git
cd atmospyre
# Install with recommended backends
pip install .[recommended]
# Or install in editable mode for development
pip install -e .[dev]
# Or minimal installation (core only)
pip install .
Available extras
minimalmodbus- Modbus communication backendschedule- Scheduling backendrecommended- Recommended setup for most users (minimalmodbus + schedule)all- All available backendsdev- Development dependencies (includes pytest, coverage tools, and backends)
Requirements
AtmosPyre requires Python 3.10+ and the following dependencies:
- multipledispatch (≥0.6.0) - Type-based dispatch for sensor tags
The currently implemented and tested backends are:
- minimalmodbus (≥2.0.0) - Modbus RTU communication
- schedule (≥1.2.2) - Job scheduling
These are automatically installed when you pip install atmospyre[recommended].
Part 1: Setup & Basic Connectivity
Finding Your Serial Port
Before you start, identify where your sensor is connected.
Linux/Mac:
# List all serial devices
ls /dev/tty*
# Common patterns:
# - /dev/ttyUSB0, /dev/ttyUSB1, ... (USB-to-serial adapters)
# - /dev/ttyACM0, /dev/ttyACM1, ... (USB CDC devices, common for Arduino-like)
# - /dev/ttyS0, /dev/ttyS1, ... (Built-in serial ports)
Important - Linux Serial Port Permissions:
On Linux, serial ports are typically owned by the dialout group. Make sure your user is part of this group:
# Check if your user is in the dialout group
groups $USER | grep dialout
# If not present, add your user to the dialout group
sudo usermod -a -G dialout $USER
# You must log out and log back in (or restart) for the changes to take effect
# Alternatively, activate the new group in the current session:
newgrp dialout
If you skip this step, you'll get a Permission denied error when trying to access the serial port.
Windows:
Step 1: Setup your Sensor
The first step is to setup your sensor with explicit settings:
⚠️ Important: About the Setup Scripts Below
The setup scripts are designed to show the full range of possibilities on how to setup your sensor. They are not designed to just run them.
If you run a setup script and the sensor's settings become out of sync with your connection settings, you may experience connection problems. Only run parts of these scripts if you need to change your sensor configuration (and keep track of the changes). If your sensor is already properly configured, skip to Part 2 to test connectivity without modifying settings.
GMP252 CO2 Sensor
📋 View GMP252 Example Code
"""GMP252 CO2 Sensor - Complete Setup Example with All Interfaces."""
from atmospyre.sensors.implementations.co2 import gmp252
from atmospyre.sensors import Stopbits, Parity, ModbusMode
# ============================================================================
# 1. BASIC SENSOR SETUP - Explicit Defaults
# ============================================================================
sensor = gmp252.GMP252(
# Connection parameters
port="/dev/ttyACM0",
slave_address=121,
# Serial communication defaults (explicitly shown)
baudrate=19200, # Default: 19200 bps
stopbits=Stopbits.TWO, # Default: TWO stop bits
bytesize=8, # Default: 8 bits
parity=Parity.NONE, # Default: NONE
modbus_mode=ModbusMode.RTU, # Default: RTU mode
timeout=0.5, # Default: 0.5 seconds
backend_tag=None, # Default: None (uses default backend)
)
# ============================================================================
# 2. READING COMPENSATION SETTINGS - Power-up Values
# ============================================================================
powerup_compensation = sensor.read(
[
gmp252.PRESSURE_COMPENSATION,
gmp252.TEMPERATURE_COMPENSATION,
gmp252.HUMIDITY_COMPENSATION,
gmp252.OXYGEN_COMPENSATION,
]
)
# ============================================================================
# 3. READING VOLATILE COMPENSATION SETTINGS - Runtime Values
# ============================================================================
volatile_compensation = sensor.read(
[
gmp252.VOLATILE_PRESSURE_COMPENSATION,
gmp252.VOLATILE_TEMPERATURE_COMPENSATION,
gmp252.VOLATILE_HUMIDITY_COMPENSATION,
gmp252.VOLATILE_OXYGEN_COMPENSATION,
]
)
# ============================================================================
# 4. READING SERIAL CONFIGURATION
# ============================================================================
# Read single configuration parameters
address_result = sensor.read([gmp252.MODBUS_ADDRESS])
speed_result = sensor.read([gmp252.SERIAL_SPEED])
parity_result = sensor.read([gmp252.SERIAL_PARITY])
stopbits_result = sensor.read([gmp252.SERIAL_STOP_BITS])
# Read all serial configuration at once
all_serial_config = sensor.read(
[
gmp252.MODBUS_ADDRESS,
gmp252.SERIAL_SPEED,
gmp252.SERIAL_PARITY,
gmp252.SERIAL_STOP_BITS,
]
)
# # ============================================================================
# # 5. WRITING COMPENSATION SETTINGS
# # ============================================================================
# # These write operations take effect immediately and do NOT require a power cycle.
# # They can be run safely in sequence.
# # ============================================================================
# # --- Write Single Compensation Parameters ---
# sensor.write({gmp252.PRESSURE_COMPENSATION: 1013.25})
# sensor.write({gmp252.TEMPERATURE_COMPENSATION: 25.0})
# sensor.write({gmp252.HUMIDITY_COMPENSATION: 50.0})
# sensor.write({gmp252.OXYGEN_COMPENSATION: 20.95})
# # --- Write Multiple Compensation Parameters at Once ---
# sensor.write({
# gmp252.PRESSURE_COMPENSATION: 1013.25,
# gmp252.TEMPERATURE_COMPENSATION: 20.0,
# gmp252.HUMIDITY_COMPENSATION: 45.0,
# gmp252.OXYGEN_COMPENSATION: 20.95
# })
# # ============================================================================
# # 6. WRITING COMPENSATION MODES
# # ============================================================================
# # These write operations take effect immediately and do NOT require a power cycle.
# # They can be run safely in sequence.
# # ============================================================================
# # Enable/disable compensation modes
# sensor.write({gmp252.PRESSURE_COMPENSATION_MODE: True})
# sensor.write({gmp252.TEMPERATURE_COMPENSATION_MODE: 2}) # 0=Off, 1=Given, 2=Measured
# sensor.write({gmp252.HUMIDITY_COMPENSATION_MODE: True})
# sensor.write({gmp252.OXYGEN_COMPENSATION_MODE: True})
# # Set CO2 filtering factor
# sensor.write({gmp252.CO2_FILTERING_FACTOR: 50})
# ============================================================================
# 7. WRITING SERIAL CONFIGURATION (Requires Power Cycle)
# ============================================================================
# ⚠️ IMPORTANT: These write operations are commented out by default!
#
# Each serial configuration write requires:
# 1. Power cycling the sensor to apply changes
# 2. Updating the sensor initialization (lines 16-27) to match new settings
# 3. Re-running this script to verify
#
# DO NOT uncomment multiple serial write operations at once! Each write must
# be followed by a power cycle and code update before the next write operation.
# ============================================================================
# --- Example A: Write Modbus Address ---
# Uncomment to change Modbus address to 5:
#
# sensor.write({
# gmp252.MODBUS_ADDRESS: 5 # Must be 1-247
# })
#
# After running:
# 1. Power cycle the sensor
# 2. Update line 17: slave_address=5
# 3. Re-run script to verify
# --- Example B: Write Serial Speed ---
# Uncomment to change serial speed to 9600 baud:
#
# sensor.write({
# gmp252.SERIAL_SPEED: 19200
# })
#
# After running:
# 1. Power cycle the sensor
# 2. Update line 20: baudrate=9600
# 3. Re-run script to verify
# --- Example C: Write Serial Parity ---
# Uncomment to change parity to Even:
#
# sensor.write({
# gmp252.SERIAL_PARITY: Parity.EVEN
# })
#
# After running:
# 1. Power cycle the sensor
# 2. Update line 23: parity=Parity.EVEN
# 3. Re-run script to verify
# --- Example D: Write Serial Stop Bits ---
# Uncomment to change stop bits to 1:
#
# sensor.write({
# gmp252.SERIAL_STOP_BITS: Stopbits.ONE
# })
#
# After running:
# 1. Power cycle the sensor
# 2. Update line 21: stopbits=Stopbits.ONE
# 3. Re-run script to verify
AlphaTRACER Radon Sensor
📋 View Alphatracer Example Code
"""AlphaTRACER Radon Sensor - Complete Setup Example with All Interfaces."""
from atmospyre.sensors.implementations.radon import alphatracer
from atmospyre.sensors import Stopbits, Parity, ModbusMode
# ============================================================================
# 1. BASIC SENSOR SETUP - Explicit Defaults
# ============================================================================
sensor = alphatracer.AlphaTRACER(
# Connection parameters
port="/dev/ttyACM0",
slave_address=122,
# Serial communication defaults
baudrate=9600, # Default: 9600 bps
stopbits=Stopbits.TWO, # Default: TWO stop bits
bytesize=8, # Default: 8 bits
parity=Parity.NONE, # Default: NONE
modbus_mode=ModbusMode.RTU, # Default: RTU mode
timeout=0.5, # Default: 0.5 seconds
backend_tag=None, # Default: None (uses default backend minimalmodbus)
)
# ============================================================================
# 2. READING CONFIGURATION - Serial Settings
# ============================================================================
# Read single configuration parameter
address_result = sensor.read([alphatracer.MODBUS_ADDRESS])
baudrate_result = sensor.read([alphatracer.BAUDRATE])
# Read multiple configuration parameters at once
all_config = sensor.read([alphatracer.MODBUS_ADDRESS, alphatracer.BAUDRATE])
# ============================================================================
# 3. WRITING CONFIGURATION - Serial Settings (Requires Power Cycle)
# ============================================================================
# ⚠️ IMPORTANT: These write operations are commented out by default!
#
# Each write operation changes the sensor's settings, which requires:
# 1. Power cycling the sensor to apply changes
# 2. Updating the sensor initialization (lines 14-26) to match new settings
# 3. Re-running this script to verify
#
# DO NOT uncomment multiple write operations at once! Each write must be
# followed by a power cycle and code update before the next write operation.
# ============================================================================
# --- Example A: Write Single Configuration Parameter ---
# Uncomment to change baudrate to 9600 bps:
#
# sensor.write({
# alphatracer.BAUDRATE: 9600
# })
#
# After running:
# 1. Power cycle the sensor
# 2. Update line 20: baudrate=9600
# 3. Re-run script to verify
Part 2: Test Single Read and Metadata Extraction
Test that your sensor is working with a simple read:
GMP252 CO2 Sensor
📋 View GMP252 Example Code
from atmospyre.sensors.implementations.co2 import gmp252
# 1. Create a sensor instance
sensor = gmp252.GMP252(port="/dev/ttyACM0", slave_address=10, baudrate=19200)
# 2. Read measurements
result = sensor.read([gmp252.CO2, gmp252.MEASURED_TEMPERATURE, gmp252.DEVICE_STATUS])
print(f"CO2: {result[gmp252.CO2]:.2f} {gmp252.CO2.metadata.unit}")
print(
f"Temperature: {result[gmp252.MEASURED_TEMPERATURE]:.2f} {gmp252.MEASURED_TEMPERATURE.metadata.unit}"
)
print(f"Status: {result[gmp252.DEVICE_STATUS]}")
# 3. Extract metadata through ReadTag interface
for tag in [gmp252.CO2, gmp252.MEASURED_TEMPERATURE, gmp252.DEVICE_STATUS]:
tag.extract_metadata()
tag.print_metadata()
Alphatracer Radon Sensor
📋 View AlphaTRACER Example Code
from atmospyre.sensors.implementations.radon import alphatracer
# 1. Create a sensor instance
sensor = alphatracer.AlphaTRACER(port="/dev/ttyACM0", slave_address=122, baudrate=19200)
# 2. Read measurements
result = sensor.read(
[alphatracer.RADON_LIVE, alphatracer.RADON_24H, alphatracer.RADON_LONG_TERM]
)
print(
f"Radon (Live): {result[alphatracer.RADON_LIVE]} {alphatracer.RADON_LIVE.metadata.unit}"
)
print(
f"Radon (24h): {result[alphatracer.RADON_24H]} {alphatracer.RADON_24H.metadata.unit}"
)
print(
f"Radon (Long-term): {result[alphatracer.RADON_LONG_TERM]} {alphatracer.RADON_LONG_TERM.metadata.unit}"
)
# 3. Extract metadata through ReadTag interface
for tag in [alphatracer.RADON_LIVE, alphatracer.RADON_24H, alphatracer.RADON_LONG_TERM]:
tag.extract_metadata()
tag.print_metadata()
Part 3: Basic Usage - Simple Logging
Once connectivity is established, the next step is setting up basic periodic logging.
GMP252 CO2 Sensor
Create a basic logging application that reads CO2 measurements every 10 seconds:
📋 View GMP252 Example Code
from atmospyre.sensors.implementations.co2 import gmp252
from atmospyre.loggers import SensorLogger
from atmospyre.scheduler.logger_scheduler import LoggerScheduler
from atmospyre.scheduler.schedule.schedule_backend import ScheduleTag
import time
# 1. Create a sensor instance
sensor = gmp252.GMP252(
port="/dev/ttyACM0", # Your serial port
slave_address=121, # Your sensor's Modbus address
)
# 2. Create a logger
logger = SensorLogger(
sensor=sensor,
tags=[gmp252.CO2_INT, gmp252.CO2], # What to measure
interval_seconds=10, # How often to log
output_path="./CO2ProbeData", # Where to save
)
# 3. Create a scheduler
scheduler = LoggerScheduler(
scheduler_dispatch_tag=ScheduleTag(), log_path="./scheduler_logs"
)
# 4. Add the logger to the scheduler
scheduler.add_logger(logger=logger)
# 5. Run the scheduler
while True:
scheduler.run_pending()
time.sleep(1)
AlphaTRACER Radon Sensor
📋 View Alphatracer Example Code
from atmospyre.sensors.implementations.radon import alphatracer
from atmospyre.loggers import SensorLogger
from atmospyre.sensors import Stopbits
from atmospyre.scheduler.logger_scheduler import LoggerScheduler
from atmospyre.scheduler.schedule.schedule_backend import ScheduleTag
import time
# 1. Create a sensor instance
sensor = alphatracer.AlphaTRACER(
port="/dev/ttyACM0", # Your serial port
slave_address=122,
stopbits=Stopbits.ONE,
baudrate=19200, # Your sensor's Modbus address
)
print(
sensor.read(
[alphatracer.RADON_LIVE, alphatracer.BAUDRATE, alphatracer.MODBUS_ADDRESS]
)
)
# 2. Create a logger
logger = SensorLogger(
sensor=sensor,
tags=[alphatracer.RADON_LIVE], # What to measure
interval_seconds=600, # How often to log
output_path="./RadonProbeData", # Where to save
)
# 3. Create a scheduler
scheduler = LoggerScheduler(
scheduler_dispatch_tag=ScheduleTag(), log_path="./scheduler_logs"
)
# 4. Add the logger to the scheduler
scheduler.add_logger(logger=logger)
# 5. Run the scheduler
while True:
scheduler.run_pending()
time.sleep(1)
Part 4: Advanced Usage - Multi-Sensor Logging
Monitor multiple sensors simultaneously with different Modbus configurations.
Multi-Sensor Setup
📋 View Example Code
"""Example: Multi-sensor data logging with shared serial port.
This example demonstrates how to set up multiple sensors on the same serial
port and log their data at different intervals using the scheduler.
Hardware Setup:
- GMP252 CO2 sensor at address 121 (2 stop bits, 19200 baud)
- AlphaTRACER radon sensor at address 122 (1 stop bit, 19200 baud)
- Both connected to /dev/ttyACM0
The sensors automatically share the port without conflicts due to the
on-demand instrument creation pattern.
"""
import time
from atmospyre.sensors.implementations.co2 import gmp252
from atmospyre.sensors.implementations.radon import alphatracer
from atmospyre.sensors import Stopbits, Parity
from atmospyre.loggers import SensorLogger
from atmospyre.loggers.strategies.writers import CSVWriter
from atmospyre.loggers.strategies.savers import JSONMetadataSaver
from atmospyre.scheduler.logger_scheduler import LoggerScheduler
from atmospyre.scheduler.schedule.schedule_backend import ScheduleTag
def main():
"""Run the multi-sensor logging example."""
# ========================================================================
# Sensor Configuration
# ========================================================================
# CO2 Sensor - Vaisala GMP252
co2_sensor = gmp252.GMP252(
port="/dev/ttyACM0",
slave_address=10,
baudrate=19200,
stopbits=Stopbits.TWO,
parity=Parity.NONE,
# Uses default: baudrate=19200, stopbits=2
)
# Radon Sensor - RadonTech AlphaTRACER
radon_sensor = alphatracer.AlphaTRACER(
port="/dev/ttyACM0", slave_address=122, baudrate=19200, stopbits=Stopbits.ONE
)
# ========================================================================
# Logger Configuration
# ========================================================================
# CO2 Logger - logs every 10 seconds
co2_logger = SensorLogger(
sensor=co2_sensor,
tags=[
gmp252.CO2, # Floating point CO2 (ppm)
gmp252.MEASURED_TEMPERATURE, # Temperature
],
interval_seconds=10,
output_path="./CO2Probe",
writer=CSVWriter(),
metadata_saver=JSONMetadataSaver(),
)
# Radon Logger - logs every 10 minutes (600 seconds)
radon_logger = SensorLogger(
sensor=radon_sensor,
tags=[alphatracer.RADON_LIVE], # Live radon concentration (Bq/m³)
interval_seconds=600,
output_path="./RadonProbe",
writer=CSVWriter(),
metadata_saver=JSONMetadataSaver(),
)
# ========================================================================
# Scheduler Setup
# ========================================================================
scheduler = LoggerScheduler(
scheduler_dispatch_tag=ScheduleTag(), log_path="./scheduler_logs"
)
# Add loggers to scheduler
scheduler.add_logger(logger=co2_logger)
scheduler.add_logger(logger=radon_logger)
# ========================================================================
# Run
# ========================================================================
print("Starting multi-sensor data logging...")
print(" - CO2 sensor: logging every 10 seconds to ./CO2Probe")
print(" - Radon sensor: logging every 600 seconds to ./RadonProbe")
print("Press Ctrl+C to stop.\n")
try:
while True:
scheduler.run_pending()
time.sleep(1)
except KeyboardInterrupt:
print("\n\nStopping data logging...")
print("Goodbye!")
if __name__ == "__main__":
main()