Skip to content

runsascoded/awair

Repository files navigation

Awair Air Quality Dashboard

PyPI version License: MIT Dashboard

A Python CLI tool and automated data collection system for Awair air quality sensors. Provides real-time data fetching using the Awair API, historical analysis, automated S3 storage via AWS Lambda (per-device), and a web dashboard for visualization.

Awair Dashboard

Features

  • Web Dashboard: Real-time visualization at awair.runsascoded.com
  • CLI Interface: Raw data fetching, analysis, and export from Awair sensors
  • Automated Collection: AWS Lambda functions that collect data every minute per device
  • Multi-Device Support: Separate Lambda stacks and Parquet files per device
  • S3 Storage: Efficient Parquet format with incremental updates
  • Data Analysis: Built-in tools for gaps analysis, histograms, and data summaries
  • Flexible Storage: Works with local files or S3 (configurable default paths)
  • AWS Deployment: One-command CDK deployment with automatic IAM permissions

Installation

From PyPI (Recommended)

# Basic installation
pip install awair

# With Lambda deployment support
pip install awair[lambda]

# Development installation
pip install awair[dev]

From Source

git clone https://github.com/runsascoded/awair.git
cd awair
pip install -e .

Configuration

API Token

Set your Awair API token via:

  • Environment variable: export AWAIR_TOKEN="your-token"
  • Local file: echo "your-token" > .token
  • User config: echo "your-token" > ~/.awair/token

API Rate Limits:

  • Hobbyist tier (default): Advertised 500/day, observed up to 1,280/day
  • Enterprise tier: 86,400/day (60 requests/minute)
  • Contact [email protected] to request a rate limit increase
  • Thanks to Steve at Awair for upgrading this project to Enterprise tier!

Device Configuration

Configure your Awair device via:

  • Environment variables: export AWAIR_DEVICE_TYPE="awair-element" AWAIR_DEVICE_ID="12345"
  • Local file: echo "awair-element,12345" > .awair-device
  • User config: echo "awair-element,12345" > ~/.awair/device
  • Auto-discovery: If not configured, the CLI will automatically detect your device on first use
  • Command-line flag: Use -i/--device-id with numeric ID or name pattern (regex):
    • awair data info -i 17617 (numeric ID)
    • awair data info -i "Ryan" (name pattern)
    • awair data info -i "^Awair 2$" (exact regex match)

Data Storage Location

Configure default data file path via:

  • Environment variable: export AWAIR_DATA_PATH="s3://your-bucket/awair-17617.parquet" (explicit path)
  • Local file: echo "s3://your-bucket/awair-17617.parquet" > .awair-data-path
  • User config: echo "s3://your-bucket/awair-17617.parquet" > ~/.awair/data-path
  • Path template: export AWAIR_DATA_PATH_TEMPLATE="s3://your-bucket/awair-{device_id}.parquet"
    • Automatically interpolates {device_id} from device configuration
    • Default template: s3://380nwk/awair-{device_id}.parquet
    • Useful for multi-device setups where you switch between devices

Usage

Data Collection

# Fetch raw API data and save to configured data file
awair api raw --from-dt 250710T10 --to-dt 250710T11

# Fetch raw API data and output as JSONL to stdout
awair api raw --from-dt 250710T10 --to-dt 250710T11 -d /dev/null

# Fetch only new data since latest timestamp in storage
awair api raw --recent-only

# Check your account info
awair api self

# List your devices (cached, 1 hour TTL)
awair api devices

# Force refresh device list from API
awair api devices --refresh

Data Analysis

# Show data file summary
awair data info
awair data info -d s3://your-bucket/data.parquet

# Daily histogram of record counts
awair data hist
awair data hist --from-dt 250710 --to-dt 250712

# Find timing gaps in data
awair data gaps -n 5 -m 300  # Top 5 gaps over 5 minutes

AWS Lambda Deployment

# Deploy automated data collector for a device (1-minute intervals)
awair lambda deploy -s awair-updater-17617 -r 1

# Deploy for multiple devices (separate stacks)
AWAIR_DEVICE_ID=17617 AWAIR_DATA_PATH=s3://bucket/awair-17617.parquet \
  awair lambda deploy -s awair-updater-17617 -r 1

AWAIR_DEVICE_ID=137496 AWAIR_DATA_PATH=s3://bucket/awair-137496.parquet \
  awair lambda deploy -s awair-updater-137496 -r 1

# View CloudFormation template
awair lambda synth

# Monitor logs (specify function name)
aws logs tail /aws/lambda/awair-updater-17617 --follow

# Test locally
awair lambda test

Data Format

Sensor data is stored in Parquet format with these fields:

Field Type Description
timestamp datetime UTC timestamp
temp float Temperature (°F)
co2 int CO2 (ppm)
pm10 int PM10 particles
pm25 int PM2.5 particles
humid float Humidity (%)
voc int Volatile Organic Compounds

Example Data

{"timestamp":"2025-07-05T22:22:06.331Z","temp":73.36,"co2":563,"pm10":3,"pm25":2,"humid":52.31,"voc":96}
{"timestamp":"2025-07-05T22:21:06.063Z","temp":73.33,"co2":562,"pm10":3,"pm25":2,"humid":52.23,"voc":92}

Architecture

Automated Data Collection

The system uses AWS Lambda for automated data collection:

  • Schedule: Runs every minute via EventBridge (configurable)
  • Multi-Device: Separate Lambda stack per device
  • Storage: Updates device-specific S3 Parquet file incrementally
  • Efficiency: Only fetches data since last update
  • Reliability: Uses utz.s3.atomic_edit for safe concurrent updates
  • Scalability: Each device has its own schedule and S3 path

CLI Integration

The CLI seamlessly works with both local files and S3:

# Both work the same way
storage = ParquetStorage('local-file.parquet')
storage = ParquetStorage('s3://bucket/file.parquet')

Configurable Deployments

Lambda deployments respect user configuration:

  • IAM permissions generated dynamically per S3 bucket
  • Environment variables passed to Lambda runtime
  • Support for any S3 bucket/key combination

Development

Setup

pip install -e ".[dev]"

Code Style

ruff check
ruff format

Testing

pytest

Lambda Deployment

Deploy AWS Lambda function for automated data collection:

# Deploy latest PyPI version for a device (recommended)
AWAIR_DATA_PATH=s3://bucket/awair-17617.parquet \
  awair lambda deploy -s awair-updater-17617 -r 1

# Deploy specific PyPI version
awair lambda deploy -v 0.0.1 -s awair-updater-17617 -r 1

# Deploy from local source (development)
awair lambda deploy -v source -s awair-updater-17617 -r 1

# Build package only (no deploy)
awair lambda deploy --dry-run

Multi-Device Deployment: Each device gets its own Lambda stack with independent:

  • EventBridge schedule (default: 1 minute)
  • S3 Parquet file (awair-{device_id}.parquet)
  • CloudWatch logs
  • IAM permissions

PyPI Deployment (Default):

  • Exact Versions: Deploy specific, tested releases
  • Immutable: Consistent across environments
  • Traceable: Clear version tracking in Lambda
  • Production Ready: Uses published releases

Source Deployment (-v source):

  • 🔧 Development: Test local changes before publishing
  • 🚀 Latest Features: Access unreleased functionality

AWS Infrastructure

Each Lambda deployment creates:

  • Lambda Function: awair-updater-{device_id} (e.g., awair-updater-17617)
  • EventBridge Rule: Configurable schedule (default: 1 minute)
  • IAM Role: S3 permissions for device-specific target path
  • CloudWatch Logs: 2-week retention
  • Environment Variables: AWAIR_TOKEN, AWAIR_DATA_PATH, AWAIR_DEVICE_ID

Example: Two devices

Stack: awair-updater-17617
  ├─ Lambda: awair-updater-17617
  ├─ EventBridge: rate(1 minute)
  └─ S3: s3://380nwk/awair-17617.parquet

Stack: awair-updater-137496
  ├─ Lambda: awair-updater-137496
  ├─ EventBridge: rate(1 minute)
  └─ S3: s3://380nwk/awair-137496.parquet

Required AWS Permissions

For deployment, you need permissions to create:

  • Lambda functions and layers
  • IAM roles and policies
  • EventBridge rules
  • CloudWatch log groups
  • S3 bucket access (for your target bucket)

OG Image Generation

The dashboard uses @rdub/og-lambda to generate dynamic Open Graph images for social media previews. A scheduled Lambda takes screenshots of the dashboard hourly and uploads to S3.

Setup:

cd www
pnpm add -D @rdub/og-lambda

Configuration (.og-lambda.json):

{
  "stackName": "awair-og",
  "screenshotUrl": "https://awair.runsascoded.com/?og&t=-3d",
  "s3Bucket": "380nwk",
  "s3Key": "awair/og-image.jpg",
  "scheduleRateMinutes": 60,
  "waitForFunction": "window.chartReady",
  "timezone": "America/New_York"
}

Commands:

og-lambda status   # Check Lambda status and schedule
og-lambda deploy   # Deploy/update the Lambda
og-lambda invoke   # Manually trigger a screenshot
og-lambda logs     # Tail CloudWatch logs

The ?og URL parameter triggers a screenshot-optimized view (larger fonts, no controls).

Date/Time Format

The CLI uses a compact date format for convenience:

  • 250710 → July 10, 2025
  • 250710T16 → July 10, 2025 at 4 PM
  • 20250710T1630 → July 10, 2025 at 4:30 PM

License

MIT License - see LICENSE file for details.