Skip to main content

Python SDK for controlling Alnor ventilation devices via Modbus TCP and cloud API

Project description

🤖 AI-Generated Code: This entire SDK was generated by Claude Code. All code, documentation, tests, and CI/CD infrastructure were created through AI assistance.

Alnor SDK

Python SDK for controlling Alnor ventilation devices via Modbus TCP (local) or Cloud API (remote).

CI Python 3.13+ License: MIT Code style: black

⚠️ Development Status: This SDK is in active development and requires Python 3.13+ for modern type system features.

⚠️ Testing Status: Currently tested only with HRU-PremAir-450 heat recovery unit via Cloud API. Modbus TCP functionality is completely untested and may not work correctly. If you test with Modbus or other devices, please report your results!

🚀 Quick Start

New to the SDK? Check out the Quick Start Guide for a 5-minute tutorial!

from alnor_sdk import AlnorClient, ProductType

async with AlnorClient() as client:
    device = await client.connect("my_hru", "192.168.1.100",
                                   ProductType.HEAT_RECOVERY_UNIT)
    state = await client.get_state(device.device_id)
    print(f"Temperature: {state.indoor_temperature}°C, Speed: {state.speed}%")

Features

  • Two connection methods: Local Modbus TCP or cloud API
  • Command-line interface: Control devices from your terminal
  • Async/await API for non-blocking I/O
  • Tested devices:
    • HRU-PremAir-450 (Heat Recovery Unit) via Cloud API
  • Device discovery and connection management
  • Type-safe models with Pydantic
  • Comprehensive error handling
  • Protocol-based architecture for flexibility

Installation

SDK Only

pip install alnor-sdk

With CLI Tool

# Install with command-line interface
pip install alnor-sdk[cli]

# Or with interactive TUI (Terminal UI)
pip install alnor-sdk[cli,tui]

For Development

pip install alnor-sdk[dev]

CLI Tool

The SDK includes a powerful command-line interface for controlling devices:

# Connect to a device
alnor device connect 192.168.1.100 living_room --type HRU

# Check device status
alnor device status living_room

# Control device speed
alnor control speed living_room 75

# Set ventilation mode
alnor control mode living_room auto

# Monitor device in real-time
alnor monitor living_room

# Zone management
alnor zone create upstairs
alnor zone add upstairs living_room
alnor zone control upstairs 60

Features:

  • Beautiful terminal output with colors and tables
  • JSON output for scripting and automation
  • Real-time monitoring with live updates
  • Zone management for controlling multiple devices
  • Configuration persistence across sessions

See the CLI Documentation for complete usage guide.

Connection Methods

Local Modbus TCP (Direct Connection)

Connect directly to Alnor devices on your local network using the Modbus TCP protocol on port 502. This method:

  • Requires devices to have local Modbus enabled
  • Works without internet connection
  • Provides fastest response times
  • Best for home automation systems

Cloud API (Remote Connection)

Connect to devices through the Alnor cloud gateway at api.connect2myhome.eu. This method:

  • Requires Alnor cloud account (same as mobile app)
  • Works from anywhere with internet
  • Goes through internet gateway device
  • No special firewall configuration needed

Note: Many Alnor internet gateways only support cloud mode by default. If port 502 is closed on your device, use the cloud API method.

Quick Start

Local Modbus TCP Connection

import asyncio
from alnor_sdk import AlnorClient, ProductType

async def main():
    async with AlnorClient() as client:
        # Connect to a device
        device = await client.connect(
            device_id="living_room_hrv",
            host="192.168.1.100",
            product_type=ProductType.HEAT_RECOVERY_UNIT
        )

        # Set ventilation speed to 75%
        await client.set_speed(device.device_id, 75)

        # Get current state
        state = await client.get_state(device.device_id)
        print(f"Speed: {state.speed}%")
        print(f"Indoor temperature: {state.indoor_temperature}°C")
        print(f"Filter days remaining: {state.filter_days_remaining}")

asyncio.run(main())

Cloud API Connection

Option 1: Using Environment Variables (Recommended)

Set environment variables:

export ALNOR_CLOUD_USERNAME=your_email@example.com
export ALNOR_CLOUD_PASSWORD=your_password

Then use the client without passing credentials:

import asyncio
from alnor_sdk.communication import AlnorCloudApi, CloudClient
from alnor_sdk.controllers import HeatRecoveryUnitController
from alnor_sdk.models.enums import VentilationMode

async def main():
    # Credentials loaded automatically from environment variables
    async with AlnorCloudApi() as api:
        await api.connect()

        # Get your bridges (gateways)
        bridges = await api.get_bridges()
        bridge_id = bridges[0]["bridgeId"]

        # Get devices
        devices = await api.get_devices(bridge_id)
        device_id = devices[0]["deviceId"]
        product_id = devices[0]["productId"]

        # Create register-based client and controller
        client = CloudClient(api, device_id)
        controller = HeatRecoveryUnitController(client, device_id, product_id)

        # Set ventilation mode
        await controller.set_mode(VentilationMode.HOME)

        # Get current state
        state = await controller.get_state()
        print(f"Speed: {state.speed}%")
        print(f"Indoor temperature: {state.indoor_temperature}°C")
        print(f"Filter: {state.filter_days_remaining} days remaining")

asyncio.run(main())

Option 2: Passing Credentials Directly

async with AlnorCloudApi("your_email@example.com", "your_password") as api:
    await api.connect()
    # ... rest of code

See examples/cloud_control.py for a complete cloud API example.

Supported Devices

⚠️ Testing Status: Only the HRU-PremAir-450 has been tested and verified to work. All other devices listed below are based on product IDs found in the SDK but remain completely untested. If you test any of these devices, please report your results!

Exhaust Fans

  • ⚠️ VMC-02VJ04 (Product ID: 0001c844) - UNTESTED

Heat Recovery Units

  • HRU-PremAir-450 - TESTED & WORKING (via Cloud API)
  • ⚠️ VMD-02RMS37-2 (Product ID: 0001c89f) - UNTESTED
  • ⚠️ VMD-02RPS54 (Product ID: 0001c84f) - UNTESTED
  • ⚠️ VMD-02RPS66 (Product ID: 0001c88e) - UNTESTED
  • ⚠️ VMD-02RPS78 (Product ID: 0001c8a0) - UNTESTED

CO2 Sensors

  • ⚠️ VMS-02C05 (Product ID: 0001c845) - UNTESTED
  • ⚠️ VMI-02MC02 (Product ID: 0001c86c) - UNTESTED

Humidity Sensors

  • ⚠️ VMS-02HB04 (Product ID: 0001c846) - UNTESTED
  • ⚠️ VMI-02MPH04 (Product ID: 0001c86a) - UNTESTED

Usage Examples

Device Discovery

async with AlnorClient() as client:
    # Scan network for devices
    devices = await client.discover_devices([
        "192.168.1.100",
        "192.168.1.101",
        "192.168.1.102"
    ])

    for device in devices:
        print(f"Found: {device.device_id} at {device.host}")

Reading Sensors

async with AlnorClient() as client:
    # Connect to CO2 sensor
    device = await client.connect(
        device_id="bedroom_co2",
        host="192.168.1.101",
        product_type=ProductType.CO2_SENSOR_VMS
    )

    # Read CO2 level
    co2_level = await client.read_co2(device.device_id)
    print(f"CO2: {co2_level} ppm")

    # Connect to humidity sensor
    device = await client.connect(
        device_id="bathroom_humidity",
        host="192.168.1.102",
        product_type=ProductType.HUMIDITY_SENSOR_VMS
    )

    # Read temperature and humidity
    temperature, humidity = await client.read_humidity(device.device_id)
    print(f"Temperature: {temperature}°C, Humidity: {humidity}%")

Zone Management

async with AlnorClient() as client:
    # Connect devices
    await client.connect("living_room", "192.168.1.100", ProductType.HEAT_RECOVERY_UNIT)
    await client.connect("bedroom", "192.168.1.101", ProductType.EXHAUST_FAN)

    # Create zone
    zone = await client.create_zone("upstairs", "Upstairs Zone")

    # Add devices to zone
    await client.add_device_to_zone("upstairs", "living_room")
    await client.add_device_to_zone("upstairs", "bedroom")

    # Control all devices in zone
    await client.control_zone("upstairs", speed=60)

Ventilation Modes

from alnor_sdk import VentilationMode

async with AlnorClient() as client:
    device = await client.connect("hrv", "192.168.1.100", ProductType.HEAT_RECOVERY_UNIT)

    # Set mode
    await client.set_mode(device.device_id, VentilationMode.AUTO)

    # Available modes:
    # - VentilationMode.AWAY  (low speed)
    # - VentilationMode.HOME  (medium speed)
    # - VentilationMode.AUTO  (automatic)
    # - VentilationMode.PARTY (high speed)

Architecture

Protocol

  • Modbus TCP: Industry-standard protocol for device communication
  • Port: 502 (default Modbus TCP port)
  • Registers: Holding registers (40001-49999 range)

Models

  • Device: Physical device representation
  • DeviceState: Current state including sensor readings
  • Product: Product catalog information
  • Zone: Logical grouping of devices

Controllers

  • ExhaustFanController: Controls exhaust fan devices
  • HeatRecoveryUnitController: Controls heat recovery units
  • SensorController: Reads sensor data

Development

Setup

git clone https://github.com/nashant/alnor-sdk
cd alnor-sdk
python -m venv venv
source venv/bin/activate
pip install -e .[dev]

Testing

# Run tests
pytest

# Run with coverage
pytest --cov=alnor_sdk --cov-report=html

# Type checking
mypy src/alnor_sdk

# Linting
pylint src/alnor_sdk

# Formatting
black src/ tests/
isort src/ tests/

Building

python -m build

Protocol Details

Modbus Register Mapping

Exhaust Fan (VMC-02VJ04)

  • 41001: Fan speed (read)
  • 41003: Ventilation speed (read)
  • 41051: Ventilation speed set (write)

Heat Recovery Unit (VMD-02RMS37-2, etc.)

  • 41000: Ventilation speed (read)
  • 41001-41002: Fan speeds (read)
  • 41005-41011: Temperatures (read, value/10)
  • 41040: Filter days remaining (read)
  • 41500: Ventilation speed set (write)

Sensors

  • CO2: 41000 (read, ppm)
  • Humidity: 41000 (temperature, value/10), 41001 (humidity %)

Troubleshooting

Local Modbus Connection Issues

  • Ensure device is powered and connected to network
  • Verify IP address and port 502 is accessible
  • Check firewall settings
  • Port 502 closed? Your gateway may only support cloud mode - use Cloud API instead

Cloud Authentication Issues

  • Error 10103 (Invalid credentials):
    • Verify username/password in the Alnor mobile app
    • Check for verification emails from alnor.eu or connect2myhome.eu
    • Try resetting password in the mobile app
    • Ensure account is verified (click email verification link)
  • Error 10100 (User not found):
    • Create account in the Alnor mobile app first
  • Error 10102 (Account locked):
    • Contact Alnor support to unlock account

Timeout Errors

  • Increase timeout: AlnorClient(timeout=10.0) or AlnorCloudApi(username, password, timeout=15.0)
  • Check network latency
  • For cloud API: Verify gateway is online and connected

Register Read/Write Errors

  • Verify device type matches ProductType
  • Check Modbus register addresses for your device model
  • For cloud API: Check device is connected to gateway

Testing

The SDK includes comprehensive unit and integration tests.

Running Unit Tests

Unit tests require no external dependencies (devices or network):

# Run all unit tests
pytest tests/unit -m unit -v

# Run with coverage report
pytest tests/unit --cov=alnor_sdk --cov-report=html

# View coverage report
open htmlcov/index.html

Running Integration Tests

Integration tests require real devices and credentials:

# Set up environment variables
export ALNOR_DEVICE_HOST=192.168.1.100
export ALNOR_CLOUD_USERNAME=your_email@example.com
export ALNOR_CLOUD_PASSWORD=your_password

# Run integration tests
pytest tests/integration -m integration -v

# Run all tests
pytest -v

Required environment variables for integration tests:

  • ALNOR_DEVICE_HOST: IP address of your Modbus device
  • ALNOR_CLOUD_USERNAME: Your Alnor cloud email
  • ALNOR_CLOUD_PASSWORD: Your Alnor cloud password

Test Markers

Tests are organized using pytest markers:

  • @pytest.mark.unit: Fast tests with no external dependencies
  • @pytest.mark.integration: Tests requiring real devices/network
  • @pytest.mark.slow: Long-running tests
# Run only fast unit tests
pytest -m unit

# Run everything except slow tests
pytest -m "not slow"

License

MIT License - see LICENSE file for details

Project Structure

alnor-sdk/
├── src/alnor_sdk/       # Main SDK source code
│   ├── communication/   # Modbus and Cloud API clients
│   ├── controllers/     # Device controllers
│   ├── models/          # Pydantic data models
│   └── utils/           # Utility functions and constants
├── tests/               # Test suite
│   ├── unit/            # Unit tests (no external dependencies)
│   └── integration/     # Integration tests (requires devices)
├── examples/            # Usage examples
│   ├── basic_connection.py
│   ├── cloud_control.py
│   └── ...
├── scripts/             # Development/debug scripts (NOT part of SDK)
│   ├── test_*.py        # Debug/integration scripts
│   └── README.md        # ⚠️ See warning about credentials
└── docs/                # Documentation

Note: The scripts/ directory contains development and debugging scripts that may have hardcoded credentials. These are not part of the official SDK API. Use the examples/ directory for proper usage examples.

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Submit a pull request

Disclaimer

This SDK is an unofficial implementation based on reverse engineering the Alnor Android app. Use at your own risk. The author is not affiliated with Alnor.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

alnor_sdk-0.3.0.tar.gz (47.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

alnor_sdk-0.3.0-py3-none-any.whl (56.8 kB view details)

Uploaded Python 3

File details

Details for the file alnor_sdk-0.3.0.tar.gz.

File metadata

  • Download URL: alnor_sdk-0.3.0.tar.gz
  • Upload date:
  • Size: 47.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for alnor_sdk-0.3.0.tar.gz
Algorithm Hash digest
SHA256 659d64dc5a79e4200ddb00fe864dd4171f4b05cff4760d2900491cfbb3ebd662
MD5 db711e2e56e41987e2c363915b7944e9
BLAKE2b-256 518a7e7e45ad496fdeb2b59536efdcf5b9ec3a8e0ddfba0f368e1fe67b04533d

See more details on using hashes here.

File details

Details for the file alnor_sdk-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: alnor_sdk-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 56.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for alnor_sdk-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9a71be01d550597f8d3bec4226cfcc18275708a589985bf7a25a5670e46e234b
MD5 b1421fa7ad03016896f7785008b15741
BLAKE2b-256 6e5f8d6688fa32cdae28af23f5c6ab9f6012f9aa55b2ccd80a324a2b335ffb81

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page