Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

32 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

uhooapi - Python Client for uHoo API

PyPI version Python versions License: MIT Code style: Ruff

A modern, asynchronous Python client for the uHoo air quality API. This library provides an intuitive, type-safe interface to access your uHoo device data, manage devices, and retrieve real-time air quality metrics with automatic token management and comprehensive error handling.

✨ Features

  • πŸš€ Async/Await Native: Built on aiohttp for high-performance, non-blocking API calls
  • πŸ” Automatic Token Management: Handles authentication, token refresh, and retry logic automatically
  • πŸ“ Full Type Annotations: Complete type hints for better IDE support and reliability
  • 🎯 Production Ready: 100% test coverage with comprehensive unit and integration tests
  • πŸ”„ Smart Error Handling: Custom exceptions with automatic retry for 401/403 errors
  • πŸ“Š Complete Sensor Coverage: Access to all uHoo metrics (temperature, humidity, COβ‚‚, PM2.5, virus index, etc.)
  • ⚑ Efficient Data Processing: Automatic averaging and rounding of sensor readings

πŸ“¦ Installation

From PyPI (Recommended)

pip install uhooapi

Development Installation

# Clone the repository
git clone https://github.com/yourusername/uhooapi.git
cd uhooapi

# 2. Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# 3. Install with dev dependencies
pip install -e ".[dev]"

# 4. Install pre-commit hooks
pre-commit install

# 5. Run tests to verify
pytest

πŸš€ Quick Start

import asyncio
import aiohttp
from uhooapi import Client

async def main():
    # Create a session and client
    async with aiohttp.ClientSession() as session:
        client = Client(
            api_key="your_uhoo_api_key_here",  # Get from uHoo dashboard
            websession=session,
            debug=True  # Enable debug logging
        )

        # Authenticate and get token
        await client.login()

        # Discover and set up your devices
        await client.setup_devices()

        # Get all devices
        devices = client.get_devices()
        print(f"πŸ“± Found {len(devices)} uHoo device(s)")

        # Get latest data for the first device
        if devices:
            first_device_serial = list(devices.keys())[0]
            await client.get_latest_data(first_device_serial)

            # Access the device data
            device = devices[first_device_serial]
            print(f"\n🏠 Device: {device.device_name}")
            print(f"πŸ“ Location: {device.room_name}")
            print(f"🌑️ Temperature: {device.temperature}°C")
            print(f"πŸ’§ Humidity: {device.humidity}%")
            print(f"☁️ COβ‚‚: {device.co2} ppm")
            print(f"πŸ’¨ PM2.5: {device.pm25} Β΅g/mΒ³")
            print(f"🦠 Virus Risk Index: {device.virus_index}")

# Run the async function
asyncio.run(main())

πŸ“– Usage Examples

πŸ”„ Continuous Monitoring

import asyncio
from datetime import datetime
from uhooapi import Client

async def monitor_air_quality(api_key: str, update_interval: int = 300):
    """Continuously monitor air quality and log changes."""
    async with aiohttp.ClientSession() as session:
        client = Client(api_key=api_key, websession=session)
        await client.login()
        await client.setup_devices()

        print("Starting air quality monitoring...")
        while True:
            for serial_number, device in client.get_devices().items():
                await client.get_latest_data(serial_number)

                print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]")
                print(f"Device: {device.device_name} ({device.room_name})")
                print("-" * 40)
                print(f"Temperature: {device.temperature:5.1f}Β°C")
                print(f"Humidity:    {device.humidity:5.1f}%")
                print(f"COβ‚‚:         {device.co2:5.0f} ppm")
                print(f"PM2.5:       {device.pm25:5.1f} Β΅g/mΒ³")
                print(f"Virus Index: {device.virus_index:5.1f}")

                # Add alerts for poor air quality
                if device.co2 > 1000:
                    print("⚠️  Warning: High COβ‚‚ levels detected!")
                if device.pm25 > 35:
                    print("⚠️  Warning: Elevated PM2.5 levels!")

            await asyncio.sleep(update_interval)

πŸ›‘οΈ Robust Error Handling

from uhooapi.errors import UnauthorizedError, ForbiddenError, RequestError

async def fetch_with_retry(client: Client, serial_number: str, max_retries: int = 3):
    """Fetch data with exponential backoff retry logic."""
    for attempt in range(max_retries):
        try:
            await client.get_latest_data(serial_number)
            return True

        except UnauthorizedError as e:
            print(f"❌ Authentication failed: {e}")
            # Re-authenticate and retry
            await client.login()
            continue

        except ForbiddenError as e:
            print(f"πŸ”’ Permission denied: {e}")
            return False

        except RequestError as e:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # Exponential backoff
                print(f"🌐 Request failed (attempt {attempt + 1}/{max_retries}), "
                      f"retrying in {wait_time}s...")
                await asyncio.sleep(wait_time)
            else:
                print(f"πŸ’₯ Max retries exceeded: {e}")
                return False

    return False

πŸ“ˆ Multi-Device Data Aggregation

async def get_environmental_summary(api_key: str):
    """Get summary statistics across all devices."""
    async with aiohttp.ClientSession() as session:
        client = Client(api_key=api_key, websession=session)
        await client.login()
        await client.setup_devices()

        devices = client.get_devices()

        # Fetch data for all devices concurrently
        tasks = [
            client.get_latest_data(serial)
            for serial in devices.keys()
        ]
        await asyncio.gather(*tasks)

        # Calculate averages
        temps = [d.temperature for d in devices.values()]
        humidities = [d.humidity for d in devices.values()]
        co2_levels = [d.co2 for d in devices.values()]

        print("\nπŸ“Š Environmental Summary")
        print("=" * 40)
        print(f"Total Devices: {len(devices)}")
        print(f"Avg Temperature: {sum(temps)/len(temps):.1f}Β°C")
        print(f"Avg Humidity: {sum(humidities)/len(humidities):.1f}%")
        print(f"Avg COβ‚‚: {sum(co2_levels)/len(co2_levels):.0f} ppm")

        # Identify problem areas
        worst_co2 = max(devices.values(), key=lambda d: d.co2)
        if worst_co2.co2 > 800:
            print(f"\n⚠️  Highest COβ‚‚ in: {worst_co2.room_name} ({worst_co2.co2} ppm)")

πŸ—οΈ Architecture

Client Class (uhooapi.client.Client)

Client(
    api_key: str,                    # Your uHoo API key
    websession: aiohttp.ClientSession,  # aiohttp session
    **kwargs                         # Optional: debug=True for debug logging
)

Device Class (uhooapi.device.Device)

device.device_name      # "Living Room"
device.serial_number    # "UHOO12345"
device.mac_address      # "AA:BB:CC:DD:EE:FF"
device.room_name        # "Living Room"
device.floor_number     # 1

device.temperature      # 22.5Β°C
device.humidity         # 45.0%
device.co2              # 800 ppm
device.pm25             # 12.3 Β΅g/mΒ³
device.virus_index      # 2.5
device.mold_index       # 1.8
device.tvoc             # 150.0 ppb
# ... and 15+ more sensors

🚨 Error Handling

The library defines custom exceptions for different error scenarios:

from uhooapi.errors import (
    UhooError,          # Base exception
    RequestError,       # General API failures
    UnauthorizedError,  # 401 - Invalid/expired token
    ForbiddenError      # 403 - Insufficient permissions
)

try:
    await client.get_latest_data("UHOO12345")
except UnauthorizedError:
    # Automatic retry with fresh login is built-in
    print("Token expired, re-authenticating...")
except ForbiddenError as e:
    print(f"Access denied: {e.message}")
except RequestError as e:
    print(f"API request failed (status: {e.status}): {e}")
except KeyError:
    print("Device not found. Did you call setup_devices()?")
except Exception as e:
    print(f"Unexpected error: {e}")

πŸ§ͺ Testing

The project includes a comprehensive test suite:

# Run all tests
pytest

# Run with coverage report
pytest --cov=src/uhooapi --cov-report=html

# Run specific test categories
pytest tests/unit/ -v           # Unit tests
pytest tests/integration/ -v    # Integration tests

# Run tests in parallel
pytest -n auto

πŸ”§ Building and Publishing

# Update version in pyproject.toml first!

# Build distribution packages
python -m build

# Check build quality
twine check dist/*

# Upload to TestPyPI (for testing)
python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*

# Upload to PyPI
python -m twine upload dist/*

πŸ“ Project Structure

uhooapi/
β”œβ”€β”€ src/uhooapi/               # Source code
β”‚   β”œβ”€β”€ __init__.py           # Package exports
β”‚   β”œβ”€β”€ client.py             # Main Client class
β”‚   β”œβ”€β”€ api.py                # Low-level API wrapper
β”‚   β”œβ”€β”€ device.py             # Device data model (22+ sensors)
β”‚   β”œβ”€β”€ errors.py             # Custom exceptions
β”‚   β”œβ”€β”€ const.py              # Constants and defaults
β”‚   β”œβ”€β”€ endpoints.py          # API endpoint configurations
β”‚   └── util.py               # Utility functions
β”œβ”€β”€ tests/                    # Test suite
β”‚   β”œβ”€β”€ unit/                # Unit tests (mocked)
β”‚   β”‚   β”œβ”€β”€ test_client.py   # Client tests
β”‚   β”‚   β”œβ”€β”€ test_api.py      # API tests
β”‚   β”‚   └── test_device.py   # Device model tests
β”‚   β”œβ”€β”€ integration/         # Integration tests
β”‚   └── conftest.py          # Test fixtures
β”œβ”€β”€ pyproject.toml           # Package configuration
β”œβ”€β”€ README.md                # This file
β”œβ”€β”€ pre-commit-config.yaml   # Code quality hooks
└── .github/workflows/       # CI/CD pipelines (optional)

🀝 Contributing

We welcome contributions! Here's how to help:

  1. Fork the repository

  2. Clone your fork: git clone https://github.com/yourusername/uhooapi.git

  3. Create a branch: git checkout -b feature/amazing-feature

  4. Make your changes and add tests

  5. Run tests: pytest && pre-commit run --all-files

  6. Commit: git commit -m 'Add amazing feature'

  7. Push: git push origin feature/amazing-feature

  8. Open a Pull Request

About

This is a API library for uHoo premium accounts.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages