# pymelcloudhome
A modern, fully asynchronous Python library for the Mitsubishi Electric "MelCloudHome" platform API, with persistent session handling.
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [`login(email: str, password: str)`](#loginemail-str-password-str)
- [`list_devices() -> List[Device]`](#list_devices---listdevice)
- [`get_device_state(device_id: str) -> Optional[Dict[str, Any]]`](#get_device_statedevice_id-str---optionaldictstr-any)
- [`set_device_state(device_id: str, device_type: str, state_data: dict) -> dict`](#set_device_statedevice_id-str-device_type-str-state_data-dict---dict)
- [`close()`](#close)
- [Caching](#caching)
- [Automatic Session Renewal](#automatic-session-renewal)
- [Error Handling](#error-handling)
- [Example of Handling Errors](#example-of-handling-errors)
- [Example Usage](#example-usage)
- [Running Tests](#running-tests)
- [Contributing](#contributing)
## Installation
For developers working on `pymelcloudhome`, you'll need [Poetry](https://python-poetry.org/docs/#installation) to manage dependencies.
1. **Clone the repository:**
```bash
git clone https://github.com/your-username/pymelcloudhome.git
cd pymelcloudhome
```
2. **Install all dependencies (production and development):**
```bash
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:
```bash
pip install pymelcloudhome
```
## 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 browser (Playwright) to handle the login process, including any JavaScript-based authentication.
```python
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.
```python
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.
```python
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, common `state_data` values you might send include:
- `"power"`: `True` or `False` (to turn the device on or off)
- `"setTemperatureZone1"`: A float representing the target temperature for Zone 1 (e.g., `22.0`)
- Other values may be available depending on your specific device model and its capabilities. You can inspect the output of `get_device_state` to discover more controllable parameters.
```python
device_id = "your-device-id"
device_type = "atwunit" # or "ataunit"
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`).
```python
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.
```python
# 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
```python
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
```python
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:
```bash
poetry install
```
Then, run pytest:
```bash
poetry run pytest
```
## Contributing
Contributions are welcome! Please read the contributing guidelines before submitting a pull request.
Raw data
{
"_id": null,
"home_page": "https://github.com/MHultman/pymelcloudhome",
"name": "pymelcloudhome",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.10",
"maintainer_email": null,
"keywords": "melcloudhome, mitsubishi, hvac, api, asyncio",
"author": "Your Name",
"author_email": "you@example.com",
"download_url": "https://files.pythonhosted.org/packages/8e/90/6e3d2a2df960c50f5642256ee13998a59ba565ce025980c73bacaba3c978/pymelcloudhome-0.1.1.tar.gz",
"platform": null,
"description": "\n# pymelcloudhome\n\nA modern, fully asynchronous Python library for the Mitsubishi Electric \"MelCloudHome\" platform API, with persistent session handling.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n - [`login(email: str, password: str)`](#loginemail-str-password-str)\n - [`list_devices() -> List[Device]`](#list_devices---listdevice)\n - [`get_device_state(device_id: str) -> Optional[Dict[str, Any]]`](#get_device_statedevice_id-str---optionaldictstr-any)\n - [`set_device_state(device_id: str, device_type: str, state_data: dict) -> dict`](#set_device_statedevice_id-str-device_type-str-state_data-dict---dict)\n - [`close()`](#close)\n - [Caching](#caching)\n- [Automatic Session Renewal](#automatic-session-renewal)\n- [Error Handling](#error-handling)\n - [Example of Handling Errors](#example-of-handling-errors)\n- [Example Usage](#example-usage)\n- [Running Tests](#running-tests)\n- [Contributing](#contributing)\n\n## Installation\n\nFor developers working on `pymelcloudhome`, you'll need [Poetry](https://python-poetry.org/docs/#installation) to manage dependencies.\n\n1. **Clone the repository:**\n\n ```bash\n git clone https://github.com/your-username/pymelcloudhome.git\n cd pymelcloudhome\n ```\n\n2. **Install all dependencies (production and development):**\n\n ```bash\n poetry install\n ```\n\nThis command will create a virtual environment and install all necessary packages, including those required for testing, linting, and type checking.\n\nIf you are a user and only want to install the library as a dependency in your project, you can use pip:\n\n```bash\npip install pymelcloudhome\n```\n\n## Usage\n\nThe `MelCloudHomeClient` provides the following asynchronous methods to interact with the MelCloud Home API:\n\n### `login(email: str, password: str)`\nLogs in to the MelCloud Home platform. This method uses a headless browser (Playwright) to handle the login process, including any JavaScript-based authentication.\n\n```python\nawait client.login(\"your-email@example.com\", \"your-password\")\n```\n\n### `list_devices() -> List[Device]`\nRetrieves 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.\n\n```python\ndevices = await client.list_devices()\nfor device in devices:\n print(f\"Device ID: {device.id}, Name: {device.given_display_name}, Type: {device.device_type}\")\n```\n\n### `get_device_state(device_id: str) -> Optional[Dict[str, Any]]`\nRetrieves 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.\n\n```python\ndevice_id = \"your-device-id\" # e.g., \"d3c4b5a6-f7e8-9012-cbad-876543210fed\"\nstate = await client.get_device_state(device_id)\nif state:\n print(f\"Device state: {state}\")\n```\n\n### `set_device_state(device_id: str, device_type: str, state_data: dict) -> dict`\nUpdates the operational state of a specific device.\n- `device_id`: The ID of the device to update.\n- `device_type`: The type of the device, either \"ataunit\" or \"atwunit\".\n- `state_data`: A dictionary containing the settings to update and their new values.\n\nFor ATW (Air-to-Water) devices, common `state_data` values you might send include:\n- `\"power\"`: `True` or `False` (to turn the device on or off)\n- `\"setTemperatureZone1\"`: A float representing the target temperature for Zone 1 (e.g., `22.0`)\n- Other values may be available depending on your specific device model and its capabilities. You can inspect the output of `get_device_state` to discover more controllable parameters.\n\n```python\ndevice_id = \"your-device-id\"\ndevice_type = \"atwunit\" # or \"ataunit\"\nnew_state = {\"power\": True, \"setTemperatureZone1\": 23.5}\nresponse = await client.set_device_state(device_id, device_type, new_state)\nprint(f\"Set device state response: {response}\")\n```\n\n### `close()`\nCloses the underlying aiohttp client session. This method is automatically called when using the client as an asynchronous context manager (`async with`).\n\n```python\nawait client.close()\n```\n\n### Caching\n\nTo 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.\n\n```python\n# Use a 10-minute cache\nclient = MelCloudHomeClient(cache_duration_minutes=10)\n```\n\nThis 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.\n\n## Automatic Session Renewal\n\nThe 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.\n\nThis makes the client more robust for long-running applications, as you do not need to manually handle session expiry.\n\n## Error Handling\n\nThe 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.\n\nThere are three main exceptions you should be prepared to handle:\n\n- **`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.\n\n- **`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.\n\n- **`DeviceNotFound`**: Raised when an operation is attempted on a device that does not exist or is not properly configured.\n\n### Example of Handling Errors\n\n```python\nimport asyncio\nfrom pymelcloudhome import MelCloudHomeClient\nfrom pymelcloudhome.errors import LoginError, ApiError, DeviceNotFound\n\nasync def main():\n async with MelCloudHomeClient() as client:\n try:\n # Attempt to log in\n await client.login(\"your-email@example.com\", \"your-password\")\n print(\"Login successful!\")\n\n # Perform operations\n devices = await client.list_devices()\n if not devices:\n print(\"No devices found.\")\n return\n\n # ... your code to interact with devices ...\n\n except LoginError:\n print(\"Login failed. Please check your email and password.\")\n except ApiError as e:\n print(f\"An API error occurred: Status {e.status} - {e.message}\")\n except DeviceNotFound:\n print(\"The specified device could not be found.\")\n except Exception as e:\n print(f\"An unexpected error occurred: {e}\")\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n## Example Usage\n\n```python\nimport asyncio\nfrom pymelcloudhome import MelCloudHomeClient\n\nasync def main():\n async with MelCloudHomeClient() as client:\n await client.login(\"your-email@example.com\", \"your-password\")\n\n # List all devices\n devices = await client.list_devices()\n print(\"Discovered Devices:\")\n for device in devices:\n print(f\" - ID: {device.id}, Name: {device.given_display_name}, Type: {device.device_type}\")\n\n if devices:\n # Get state of the first device\n first_device_id = devices[0].id\n current_state = await client.get_device_state(first_device_id)\n print(f\"Current state of {devices[0].given_display_name}: {current_state}\")\n\n # Example: Set power and temperature for an ATW unit\n if devices[0].device_type == \"atwunit\":\n print(f\"Attempting to set state for ATW unit: {devices[0].given_display_name}\")\n update_data = {\"power\": True, \"setTemperatureZone1\": 22.0}\n set_response = await client.set_device_state(first_device_id, \"atwunit\", update_data)\n print(f\"Set state response: {set_response}\")\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n## Running Tests\n\nTo run the test suite, first install the development dependencies:\n\n```bash\npoetry install\n```\n\nThen, run pytest:\n\n```bash\npoetry run pytest\n```\n\n## Contributing\n\nContributions are welcome! Please read the contributing guidelines before submitting a pull request.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A modern, fully asynchronous Python library for the Mitsubishi Electric MelCloudHome platform.",
"version": "0.1.1",
"project_urls": {
"Homepage": "https://github.com/MHultman/pymelcloudhome",
"Repository": "https://github.com/MHultman/pymelcloudhome"
},
"split_keywords": [
"melcloudhome",
" mitsubishi",
" hvac",
" api",
" asyncio"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "244bcf37a45daeb359b945cff6be2c9f77b41bb896387c1eeeedac6b0643d078",
"md5": "71c774e767c27c507f601a50335778bf",
"sha256": "a590e509df3716c86b9df08e9c2c2e74e009d479470d9eefd91cc99fbb406e2d"
},
"downloads": -1,
"filename": "pymelcloudhome-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "71c774e767c27c507f601a50335778bf",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.10",
"size": 9352,
"upload_time": "2025-07-22T15:27:02",
"upload_time_iso_8601": "2025-07-22T15:27:02.061233Z",
"url": "https://files.pythonhosted.org/packages/24/4b/cf37a45daeb359b945cff6be2c9f77b41bb896387c1eeeedac6b0643d078/pymelcloudhome-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "8e906e3d2a2df960c50f5642256ee13998a59ba565ce025980c73bacaba3c978",
"md5": "7d14428f6b76f3027ba34ed3e5e0942a",
"sha256": "347e792a8f57cda806f691227f66cc3e4b750193d987d00924a24cc46cf3992d"
},
"downloads": -1,
"filename": "pymelcloudhome-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "7d14428f6b76f3027ba34ed3e5e0942a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.10",
"size": 7976,
"upload_time": "2025-07-22T15:27:03",
"upload_time_iso_8601": "2025-07-22T15:27:03.218206Z",
"url": "https://files.pythonhosted.org/packages/8e/90/6e3d2a2df960c50f5642256ee13998a59ba565ce025980c73bacaba3c978/pymelcloudhome-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-22 15:27:03",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "MHultman",
"github_project": "pymelcloudhome",
"github_not_found": true,
"lcname": "pymelcloudhome"
}