Skip to main content

A modern, fully asynchronous Python library for the Mitsubishi Electric MelCloudHome platform.

Project description

pymelcloudhome

A modern, fully asynchronous Python library for the Mitsubishi Electric "MelCloudHome" platform API, with persistent session handling.

Table of Contents

Supported Devices

Device Type Read Update
ATA
ATW
ERV

Installation

For developers working on pymelcloudhome, you'll need Poetry to manage dependencies.

  1. Clone the repository:

    git clone https://github.com/MHultman/pymelcloudhome.git
    cd pymelcloudhome
    
  2. Install all dependencies (production and development):

    poetry install
    

This command will create a virtual environment and install all necessary packages, including those required for testing, linting, and type checking.

If you are a user and only want to install the library as a dependency in your project, you can use pip:

pip install pymelcloudhome

ARM64 / Raspberry Pi Support

The library uses Chromium for authentication. On ARM64 architectures (including Raspberry Pi), you need to install system Chromium and provide the path:

On Debian/Ubuntu/Raspberry Pi OS:

sudo apt-get update
sudo apt-get install -y chromium-browser

On Alpine Linux (common in Docker containers):

apk add --no-cache chromium

Then pass the executable path to the client:

from pymelcloudhome import MelCloudHomeClient

async with MelCloudHomeClient(
    chromium_executable_path='/usr/bin/chromium-browser'  # or '/usr/bin/chromium' on Alpine
) as client:
    await client.login("email@example.com", "password")
    devices = await client.list_devices()

Common Chromium paths:

  • Debian/Ubuntu/Raspberry Pi: /usr/bin/chromium-browser
  • Alpine Linux: /usr/bin/chromium
  • macOS ARM: /Applications/Chromium.app/Contents/MacOS/Chromium

Usage

The MelCloudHomeClient provides the following asynchronous methods to interact with the MelCloud Home API:

login(email: str, password: str)

Logs in to the MelCloud Home platform. This method uses a headless Chromium browser (Pyppeteer) to handle the login process, including any JavaScript-based authentication.

await client.login("your-email@example.com", "your-password")

list_devices() -> List[Device]

Retrieves a list of all devices associated with the logged-in user. Each Device object contains details about the unit, including its type (ataunit for Air-to-Air or atwunit for Air-to-Water) and current settings.

devices = await client.list_devices()
for device in devices:
    print(f"Device ID: {device.id}, Name: {device.given_display_name}, Type: {device.device_type}")

get_device_state(device_id: str) -> Optional[Dict[str, Any]]

Retrieves the current operational state of a specific device from the cached data. This method does not make a new API call. It returns a dictionary of the device's settings or None if the device is not found.

device_id = "your-device-id" # e.g., "d3c4b5a6-f7e8-9012-cbad-876543210fed"
state = await client.get_device_state(device_id)
if state:
    print(f"Device state: {state}")

set_device_state(device_id: str, device_type: str, state_data: dict) -> dict

Updates the operational state of a specific device.

  • device_id: The ID of the device to update.
  • device_type: The type of the device, either "ataunit" or "atwunit".
  • state_data: A dictionary containing the settings to update and their new values.

For ATW (Air-to-Water) devices, you can send a dictionary with the following keys:

{
  "power": true, // or false
  "setTankWaterTemperature": 55
  "forcedHotWaterMode": true, // or false
  "operationModeZone1": "HeatRoomTemperature", // "HeatFlowTemperature", "HeatCurve"
  "setTemperatureZone1": 22,
  "setHeatFlowTemperatureZone1": 45,
  "setCoolFlowTemperatureZone1": 18,
  "operationModeZone2": "HeatRoomTemperature", // "HeatFlowTemperature", "HeatCurve"
  "setTemperatureZone2": 21,
  "setHeatFlowTemperatureZone2": 40,
  "setCoolFlowTemperatureZone2": 19
}

Sending value null will leave the setting unchanged.

Here is an example of how to use this method:

device_id = "your-device-id"
device_type = "atwunit"
new_state = {"power": True, "setTemperatureZone1": 23.5}
response = await client.set_device_state(device_id, device_type, new_state)
print(f"Set device state response: {response}")

close()

Closes the underlying aiohttp client session. This method is automatically called when using the client as an asynchronous context manager (async with).

await client.close()

Caching

To minimize API calls and improve performance, the MelCloudHomeClient caches the user profile data. By default, this cache lasts for 5 minutes. You can configure this duration by passing the cache_duration_minutes parameter when creating the client.

# Use a 10-minute cache
client = MelCloudHomeClient(cache_duration_minutes=10)

This means that subsequent calls to list_devices() and get_device_state() within this timeframe will use the cached data instead of making a new API request to fetch the user context.

Automatic Session Renewal

The client is designed to be resilient to session expiry. If an API call fails with a 401 Unauthorized status, the library will automatically attempt to re-authenticate using the credentials you provided during the initial login call. If the re-login is successful, the original request will be retried automatically.

This makes the client more robust for long-running applications, as you do not need to manually handle session expiry.

Error Handling

The library uses custom exceptions to indicate specific types of failures. It is best practice to wrap your client calls in a try...except block to handle these potential errors gracefully.

There are three main exceptions you should be prepared to handle:

  • LoginError: Raised when the initial authentication with MELCloud fails. This is typically caused by incorrect credentials (email or password) or a change in the MELCloud login page. It does not contain an HTTP status code, as it originates from the browser automation process.

  • ApiError: Raised for any failed API call that does not resolve after a potential re-login attempt. This can happen if the API endpoint is not found, the server returns an error, or if a re-login attempt also fails. This exception contains a .status attribute with the HTTP status code (e.g., 404, 500) and a .message attribute with the error details from the server.

  • DeviceNotFound: Raised when an operation is attempted on a device that does not exist or is not properly configured.

Example of Handling Errors

import asyncio
from pymelcloudhome import MelCloudHomeClient
from pymelcloudhome.errors import LoginError, ApiError, DeviceNotFound

async def main():
    async with MelCloudHomeClient() as client:
        try:
            # Attempt to log in
            await client.login("your-email@example.com", "your-password")
            print("Login successful!")

            # Perform operations
            devices = await client.list_devices()
            if not devices:
                print("No devices found.")
                return

            # ... your code to interact with devices ...

        except LoginError:
            print("Login failed. Please check your email and password.")
        except ApiError as e:
            print(f"An API error occurred: Status {e.status} - {e.message}")
        except DeviceNotFound:
            print("The specified device could not be found.")
        except Exception as e:
            print(f"An unexpected error occurred: {e}")

if __name__ == "__main__":
    asyncio.run(main())

Example Usage

import asyncio
from pymelcloudhome import MelCloudHomeClient

async def main():
    async with MelCloudHomeClient() as client:
        await client.login("your-email@example.com", "your-password")

        # List all devices
        devices = await client.list_devices()
        print("Discovered Devices:")
        for device in devices:
            print(f"  - ID: {device.id}, Name: {device.given_display_name}, Type: {device.device_type}")

        if devices:
            # Get state of the first device
            first_device_id = devices[0].id
            current_state = await client.get_device_state(first_device_id)
            print(f"Current state of {devices[0].given_display_name}: {current_state}")

            # Example: Set power and temperature for an ATW unit
            if devices[0].device_type == "atwunit":
                print(f"Attempting to set state for ATW unit: {devices[0].given_display_name}")
                update_data = {"power": True, "setTemperatureZone1": 22.0}
                set_response = await client.set_device_state(first_device_id, "atwunit", update_data)
                print(f"Set state response: {set_response}")

if __name__ == "__main__":
    asyncio.run(main())

Running Tests

To run the test suite, first install the development dependencies:

poetry install

Then, run pytest:

poetry run pytest

Development Workflow

Managing Dependencies

When you modify pyproject.toml (add/remove/update dependencies), you need to update the poetry.lock file:

# Update poetry.lock file
poetry lock

# Or use the provided scripts:
# Windows:
.\scripts\update-lock.bat

# Unix/Linux/macOS:
./scripts/update-lock.sh

# Or use Make (if available):
make lock-update

Automated Checks

This project includes several automation tools to prevent common issues:

  1. Pre-commit hooks: Automatically run linting, formatting, and dependency checks before commits
  2. CI/CD validation: GitHub Actions will check that poetry.lock is up-to-date
  3. VSCode tasks: Use Ctrl+Shift+P → "Tasks: Run Task" → "Poetry: Update Lock File"

Available Make Commands

make help          # Show all available commands
make install       # Install dependencies
make lock-check    # Check if poetry.lock is up-to-date
make lock-update   # Update poetry.lock
make test          # Run tests
make lint          # Run linting and type checking
make format        # Format code
make check         # Run all checks (lock, lint, test)
make update        # Update lock and run tests

Linting and Type Checking

This project uses black and ruff for linting and mypy for type checking. To run them, ensure you have installed the development dependencies:

poetry install

Then, execute the following commands:

poetry run black .
poetry run ruff .
poetry run mypy .

Contributing

Contributions are welcome! Please read the contributing guidelines before submitting a pull request.

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

pymelcloudhome-0.3.1.tar.gz (17.3 kB view details)

Uploaded Source

Built Distribution

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

pymelcloudhome-0.3.1-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file pymelcloudhome-0.3.1.tar.gz.

File metadata

  • Download URL: pymelcloudhome-0.3.1.tar.gz
  • Upload date:
  • Size: 17.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.10.18 Linux/6.11.0-1018-azure

File hashes

Hashes for pymelcloudhome-0.3.1.tar.gz
Algorithm Hash digest
SHA256 92949b82dee857ff5aab0eaf68efca7da5b6fcbef8fab1d32f9b75a253c275da
MD5 6662494e6bf92c831c21fb3cba8debc4
BLAKE2b-256 cf8d3e4ce68a7661ce97b9c116d95cf4f1e63224a7c09eb84ab30253a499f6d3

See more details on using hashes here.

File details

Details for the file pymelcloudhome-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: pymelcloudhome-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.10.18 Linux/6.11.0-1018-azure

File hashes

Hashes for pymelcloudhome-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 39f3159a9831e06d8248459b6b970bef25476c225224fcb68f4c7797bfa0a414
MD5 865ff4c0dc357e703eead2177355263c
BLAKE2b-256 9a64d80d9f355dc7b8b195cf57d03cdf4117e1f85ad055b4784a5b204c4d57cb

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