Async Python client for Miele@Home devices
Project description
AsyncMiele
An asynchronous Python client for Miele@Home devices. It's a full rewrite of the home-assistant-miele-mobile project with a focus of decoupling the API from Home Assistant. This library also integrates some functionality from the MieleRESTServer project for those who can't afford running an extra server. It also integrates some convenience functions from the pymiele project, but doesn't require to communicate thru Miele cloud.
This library provides a simple, asynchronous interface to communicate with Miele appliances that support the Miele@Home protocol over local network (e.g., via XKM3100W module or built-in WiFi).
Why a new library?
The original home-assistant-miele-mobile project is a great project, but it's tightly coupled to Home Assistant. Those, who don't use Home Assistant, can't use this project. The original MieleRESTServer project is a great project, but it requires to run an extra REST server, which can be too much to run on a home automation controller. The original pymiele project is a great project, but it requires to communicate thru Miele cloud. It is a very strong limitation for countries, which aren't supported by Miele cloud and for users, who prefer their home automation to be fully isolated on the local network.
The library is a async merge of all 3 projects, mentioned above and is designed to integrate with any home automation system, which supports asyncio.
Features
- Asynchronous API using modern Python async/await syntax
- Connect to Miele devices over local network (no cloud dependency)
- Configuration-driven service architecture for production use
- Clean 3-step device setup process
- Enhanced capability detection and management
- Comprehensive device profile system with JSON storage
- Retrieve device information and status
- Proper error handling and type hints
- Clean, object-oriented design
Installation
pip install asyncmiele
Quick Start
Configuration-Driven Service Integration (Recommended)
For production services, use the configuration-driven approach with JSON profiles:
import asyncio
from asyncmiele import Appliance
async def service_integration():
# Load device from configuration file (includes credentials, capabilities, etc.)
appliance = await Appliance.from_config_file("kitchen_oven_config.json")
# Device is ready to use with full capability checking
if appliance.has_all_capabilities("WAKE_UP", "REMOTE_START"):
await appliance.wake_up()
await appliance.remote_start(allow_remote_start=True)
# Get current status
summary = await appliance.summary()
print(f"Progress: {summary.progress}")
asyncio.run(service_integration())
Manual Client Setup (Development/Testing)
For development or when you need manual control:
import asyncio
from asyncmiele import MieleClient, Appliance
async def manual_setup():
# Create client with stored credentials
client = MieleClient.from_hex(
host="192.168.1.100",
group_id_hex="your_group_id",
group_key_hex="your_group_key"
)
async with client:
# Create appliance instance
appliance = Appliance(client, "000123456789")
# Use appliance
summary = await appliance.summary()
print(f"Progress: {summary.progress}")
asyncio.run(manual_setup())
Device Setup Process
The library provides a streamlined 3-step configuration workflow for setting up new devices:
Step 1: Discover Devices in Setup Mode
Find devices that are ready for configuration:
python scripts/discover_setup_devices.py
Output shows devices with their information:
✅ Device found at 192.168.1.100
📋 Device ID: 000160829578 ← Save this ID for Step 3
🏷️ Device Type: Oven
🔧 Setup Mode: True
🔑 Temporary SSID: Miele_ABC123 ← Use this SSID for Step 2
Step 2: Configure Device WiFi
Important: Before running this script, manually connect your computer to the device's WiFi access point (e.g., "Miele_ABC123" from Step 1).
Configure the device to connect to your home WiFi network:
python scripts/configure_device_wifi.py \
--ssid "YourHomeWiFi" \
--password "your_wifi_password"
This script sends the WiFi configuration directly to the device using the same approach as MieleRESTServer. The device will disconnect from its access point and connect to your WiFi network, receiving a new IP address.
Note: The script assumes you are already connected to the device's temporary WiFi access point. This manual connection step ensures reliable communication during WiFi configuration.
Testing: Use --dry-run to see what configuration would be sent without actually sending it:
python scripts/configure_device_wifi.py --ssid "YourHomeWiFi" --password "your_wifi_password" --dry-run
Step 3: Create Device Profile
Generate the complete device configuration using the device ID from Step 1:
python scripts/create_device_profile.py \
--host 192.168.1.100 \
--device-id 000160829578 \
--device-name "Kitchen Oven" \
--output kitchen_oven_config.json
This script will:
- Generate and provision security credentials
- Detect device capabilities
- Extract the program catalog
- Create a complete JSON configuration file
Configuration File Format
The generated configuration file contains everything needed for service integration:
{
"device_id": "000160829578",
"device_type": "Oven",
"friendly_name": "Kitchen Oven",
"host": "192.168.1.100",
"timeout": 5.0,
"credentials": {
"group_id": "29dab98f50adf5b0",
"group_key": "244232e1c0abd062bf2a5f457834063f23baa8e44b1b7cfeba44c26560bc7ee901cc99d56e865729bbfbcd08fce4dba740cf6ca78dc9faba089b7d956b8bfcfc"
},
"capabilities": {
"supported": ["WAKE_UP", "REMOTE_START", "PROGRAM_CATALOG"],
"failed": ["LIGHT_CONTROL"],
"detection_date": "2024-01-15T10:30:00Z"
},
"program_catalog": {
"device_type": "Oven",
"extraction_method": "dop2_new",
"programs": {
"baking": {
"id": 1,
"name": "Baking",
"options": [
{"id": 101, "name": "Temperature", "min": 30, "max": 250}
]
}
}
}
}
Using Configuration Files in Services
Once you have a configuration file, using the device in your service is simple:
import asyncio
from asyncmiele import Appliance
async def my_service():
# Load complete device configuration
appliance = await Appliance.from_config_file("kitchen_oven_config.json")
# All capabilities are automatically available
if appliance.has_capability("REMOTE_START"):
await appliance.remote_start(allow_remote_start=True)
# Program catalog is automatically loaded
programs = await appliance.get_available_programs()
print(f"Available programs: {list(programs.keys())}")
asyncio.run(my_service())
Device Capabilities
The library provides an enhanced capability system using set-based operations for efficient capability checking:
Capability Checking with Configuration
When using configuration files, capability information is automatically loaded:
import asyncio
from asyncmiele import Appliance, DeviceCapability
async def capability_example():
appliance = await Appliance.from_config_file("device_config.json")
# Check single capability
if appliance.has_capability(DeviceCapability.WAKE_UP):
await appliance.wake_up()
# Check multiple capabilities (all required)
required_caps = [DeviceCapability.WAKE_UP, DeviceCapability.REMOTE_START]
if appliance.has_all_capabilities(*required_caps):
await appliance.wake_up()
await appliance.remote_start(allow_remote_start=True)
# Check multiple capabilities (any will do)
control_caps = [DeviceCapability.REMOTE_START, DeviceCapability.PROGRAM_CONTROL]
if appliance.has_any_capability(*control_caps):
print("Device supports some form of remote control")
# Get all supported capabilities
capabilities = appliance.get_supported_capabilities()
print(f"Supported: {[cap.name for cap in capabilities]}")
asyncio.run(capability_example())
Manual Capability Detection
For manual setup or testing, you can detect capabilities directly:
from asyncmiele import MieleClient
from asyncmiele.capabilities import detect_capabilities_as_sets
async def detect_capabilities():
client = MieleClient.from_hex(host, group_id, group_key)
async with client:
# Detect capabilities and get as sets
supported, failed = await detect_capabilities_as_sets(client, device_id)
print(f"Supported: {[cap.name for cap in supported]}")
print(f"Failed: {[cap.name for cap in failed]}")
asyncio.run(detect_capabilities())
Connection Management
The library includes built-in connection management and health monitoring when using the configuration-driven approach:
import asyncio
from asyncmiele import Appliance
async def connection_management_example():
# Configuration files include connection parameters and timeout settings
appliance = await Appliance.from_config_file("device_config.json")
# Built-in connection management handles:
# - Automatic retries
# - Connection pooling
# - Health monitoring
# - Network failure recovery
# Use appliance normally - connection management is automatic
summary = await appliance.summary()
print(f"Device status: {summary.status}")
asyncio.run(connection_management_example())
DOP2 Tree Visualization
The library includes tools for exploring and visualizing the DOP2 tree structure of Miele devices:
async def dop2_exploration():
appliance = await Appliance.from_config_file("device_config.json")
# Generate HTML visualization
await appliance.visualize_dop2_tree(
output_file="dop2_tree.html",
format_type="html",
known_only=True # Only explore known attributes
)
# Generate ASCII visualization for console
await appliance.visualize_dop2_tree(
output_file="dop2_tree.txt",
format_type="ascii"
)
asyncio.run(dop2_exploration())
You can also use the standalone script:
# Explore device DOP2 tree
python scripts/visualize_dop2_tree.py --config device_config.json --output-dir ./output
Migration from Manual Setup
If you have existing code using manual client setup, migration to the configuration-driven approach is straightforward:
Before (Manual Setup):
# Old manual approach
client = MieleClient.from_hex(host, group_id, group_key)
appliance = Appliance(client, device_id)
# Manual capability checking
if await client.has_capability(device_id, DeviceCapability.REMOTE_START):
await client.remote_start(device_id, allow_remote_start=True)
After (Configuration-Driven):
# New configuration-driven approach
appliance = await Appliance.from_config_file("device_config.json")
# Automatic capability checking
if appliance.has_capability(DeviceCapability.REMOTE_START):
await appliance.remote_start(allow_remote_start=True)
Creating Configuration from Existing Credentials
If you have existing credentials, create a configuration file:
from asyncmiele.models.device_profile import DeviceProfile
from asyncmiele.models.credentials import MieleCredentials
from asyncmiele.config.loader import save_device_profile
# Create profile from existing credentials
profile = DeviceProfile(
device_id="000123456789",
host="192.168.1.100",
timeout=5.0,
credentials=MieleCredentials(
group_id="your_group_id_hex",
group_key="your_group_key_hex"
)
)
# Save as configuration file
save_device_profile(profile, "device_config.json")
# Now use with configuration-driven approach
appliance = await Appliance.from_config_file("device_config.json")
Troubleshooting
Configuration Issues
- Invalid JSON: Use a JSON validator to check your configuration file format
- Missing credentials: Ensure the configuration file includes valid
group_idandgroup_key - Network connectivity: Verify the device's IP address in the
hostfield is correct and reachable
Device Setup Issues
- Device not found in Step 1: Make sure the device is in setup mode (usually by holding a button)
- WiFi configuration fails in Step 2:
- Ensure you are manually connected to the device's WiFi access point before running the script
- Verify the WiFi credentials and security type are correct
- Check that the device is reachable at 192.168.1.1 (or use --device-ip for different addresses)
- Profile creation fails in Step 3: Check network connectivity and device accessibility
Runtime Issues
- Device not responding: The device may be in sleep mode - capability checking will automatically wake it
- Remote start fails: Verify the device has
REMOTE_STARTcapability andallow_remote_start=Trueis set - Program start fails: Ensure the device has
PROGRAM_CONTROLcapability and a valid program catalog
For comprehensive troubleshooting, use the diagnostic script:
python scripts/device_diagnosis.py --config device_config.json
Acknowledgments
This project is based on reverse-engineering efforts of the Miele@Home protocol and inspired by the home-assistant-miele-mobile project. It has been refactored to be independent of Home Assistant and provide a clean, asynchronous API with configuration-driven service architecture.
License
MIT
Error Handling
The library provides detailed exception classes for proper error handling:
import asyncio
from asyncmiele import Appliance
from asyncmiele.exceptions.network import NetworkConnectionError, NetworkTimeoutError
from asyncmiele.exceptions.api import DeviceNotFoundError
from asyncmiele.exceptions.config import InvalidConfigurationError
async def handle_errors():
try:
# Configuration-driven approach handles most connection issues automatically
appliance = await Appliance.from_config_file("device_config.json")
await appliance.wake_up()
except InvalidConfigurationError as e:
print(f"Configuration error: {e}")
except NetworkConnectionError as e:
print(f"Connection failed: {e}")
except NetworkTimeoutError as e:
print(f"Request timed out: {e}")
except DeviceNotFoundError as e:
print(f"Device not found: {e}")
asyncio.run(handle_errors())
Waking Up a Device
If a Miele appliance has gone into power-saving "sleep" mode it returns invalid data.
Use the wake_up() helper to bring it online again:
# Configuration-driven approach
appliance = await Appliance.from_config_file("device_config.json")
await appliance.wake_up()
# Or with manual client
await client.wake_up("000123456789") # device route / ID
This sends {"DeviceAction": 2} to the device and usually completes with an empty 204-No-Content response.
Remote-Start (opt-in)
Starting a program remotely can be dangerous if the appliance is not prepared correctly.
For this reason remote-start is disabled by default. Enable it in one of two ways:
from asyncmiele.config import settings
settings.enable_remote_start = True # global once-per-process
or pass an explicit override flag per call:
await appliance.remote_start(allow_remote_start=True)
Before attempting a start you can check whether the appliance is ready:
if await appliance.can_remote_start():
await appliance.remote_start(allow_remote_start=True)
else:
print("Device not ready for remote start")
Remote-start issues {"ProcessAction": 1} to the /State resource. The device must already have a fully programmed cycle and expose the RemoteEnable flag (15).
Enhanced Appliance Class Features
The library provides an enhanced Appliance class that offers comprehensive device management:
Property Accessors
Convenient property accessors for common state information:
async def monitor_appliance():
appliance = await Appliance.from_config_file("device_config.json")
# Check if a program is running
if await appliance.is_running:
# Get the current program phase
phase = await appliance.program_phase
print(f"Current phase: {phase}")
# Get the remaining time in minutes
remaining = await appliance.remaining_time
print(f"Remaining time: {remaining} minutes")
# Get temperature information
current_temp = await appliance.current_temperature
target_temp = await appliance.target_temperature
print(f"Temperature: {current_temp}°C / Target: {target_temp}°C")
asyncio.run(monitor_appliance())
State Change Notifications
Monitor state changes with callbacks:
async def on_state_change(state):
print(f"State changed: {state}")
async def on_program_finished():
print("Program finished!")
# Send notification or trigger other actions
async def monitor_with_callbacks():
appliance = await Appliance.from_config_file("device_config.json")
# Register callbacks
await appliance.register_state_callback(on_state_change)
await appliance.register_program_finished_callback(on_program_finished)
# Later, unregister when no longer needed
await appliance.unregister_state_callback(on_state_change)
asyncio.run(monitor_with_callbacks())
Program Control with Configuration
With configuration files, program catalogs are automatically loaded:
async def program_control():
appliance = await Appliance.from_config_file("device_config.json")
# Get available programs (loaded from configuration)
programs = await appliance.get_available_programs()
print(f"Available programs: {list(programs.keys())}")
# Start a program with options
if appliance.has_capability("PROGRAM_CONTROL"):
await appliance.start_with_options(
"Normal",
temperature=60,
spin_speed=1200,
extra_rinse=True
)
print("Program started")
asyncio.run(program_control())
Configuration Validation
The library includes comprehensive configuration validation:
from asyncmiele.validation.config import ConfigurationValidator
async def validate_config():
validator = ConfigurationValidator()
# Load and validate a configuration file
profile = load_device_profile("device_config.json")
result = await validator.validate_profile(profile)
if result.success:
print("✅ Configuration is valid")
else:
print("❌ Configuration issues found:")
for issue in result.issues:
print(f" - {issue}")
asyncio.run(validate_config())
Utility Scripts
Configuration Management Scripts
The library includes streamlined scripts for device setup:
Discovery and Setup:
# Step 1: Find devices in setup mode
python scripts/discover_setup_devices.py
# Step 2: Configure WiFi (manual connection to device required)
python scripts/configure_device_wifi.py --ssid "YourWiFi" --password "password"
# Step 3: Create complete profile
python scripts/create_device_profile.py --host 192.168.1.100 --device-id 000123456789 --output config.json
Diagnostic and Maintenance:
# Device diagnosis and troubleshooting
python scripts/device_diagnosis.py --config device_config.json
# Factory reset (if needed)
python scripts/device_factory_reset.py --config device_config.json
# DOP2 tree exploration (development)
python scripts/dop2_explorer.py --config device_config.json
python scripts/visualize_dop2_tree.py --config device_config.json
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file asyncmiele-0.2.6.tar.gz.
File metadata
- Download URL: asyncmiele-0.2.6.tar.gz
- Upload date:
- Size: 143.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10d698acf579dfd44a5b49431ba2e0262cd8edb32702a692f6a9402290dcfde9
|
|
| MD5 |
af3fa140f1dc025a34f240d2051e97cc
|
|
| BLAKE2b-256 |
5a9f9eaa4e043dd6d143580400f5851b1f93cf737c225cffcd6d04c86e639ddf
|
Provenance
The following attestation bundles were made for asyncmiele-0.2.6.tar.gz:
Publisher:
release.yml on droman42/asyncmiele
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asyncmiele-0.2.6.tar.gz -
Subject digest:
10d698acf579dfd44a5b49431ba2e0262cd8edb32702a692f6a9402290dcfde9 - Sigstore transparency entry: 232362338
- Sigstore integration time:
-
Permalink:
droman42/asyncmiele@94775ff15c89aed1e60ab8d284119a86d0200135 -
Branch / Tag:
refs/tags/v0.2.6 - Owner: https://github.com/droman42
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@94775ff15c89aed1e60ab8d284119a86d0200135 -
Trigger Event:
push
-
Statement type:
File details
Details for the file asyncmiele-0.2.6-py3-none-any.whl.
File metadata
- Download URL: asyncmiele-0.2.6-py3-none-any.whl
- Upload date:
- Size: 164.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ecadc3869d6618fafa56f61f87b592fd3cfeee6241a301c3c2114c7d10dbade
|
|
| MD5 |
6f547043dc1a40aa6a4fa66f66b2aab7
|
|
| BLAKE2b-256 |
1b26a9deb87cd5094d0a29d0f3eeed175623a0efbfbe0517ce205fff87af05cb
|
Provenance
The following attestation bundles were made for asyncmiele-0.2.6-py3-none-any.whl:
Publisher:
release.yml on droman42/asyncmiele
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asyncmiele-0.2.6-py3-none-any.whl -
Subject digest:
5ecadc3869d6618fafa56f61f87b592fd3cfeee6241a301c3c2114c7d10dbade - Sigstore transparency entry: 232362352
- Sigstore integration time:
-
Permalink:
droman42/asyncmiele@94775ff15c89aed1e60ab8d284119a86d0200135 -
Branch / Tag:
refs/tags/v0.2.6 - Owner: https://github.com/droman42
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@94775ff15c89aed1e60ab8d284119a86d0200135 -
Trigger Event:
push
-
Statement type: