A comprehensive Python library for controlling Revogi-based Max Hauri MaxSmart PowerStrips and Smart Plugs
Project description
MaxSmart Python Module
Version: 2.1.0
A comprehensive Python library for controlling Revogi-based Max Hauri MaxSmart PowerStrips and Smart Plugs over local network. Features intelligent auto-detection, adaptive polling, real-time monitoring, and robust async architecture with simplified device identification.
☕ Support My Work
If this plugin helps you, consider supporting its development:
🎯 What's New in v2.1.0
- 🔄 Protocol Transparency - Unified API works seamlessly with both HTTP and UDP V3 devices
- 🤖 Automatic Protocol Detection - No need to specify protocol, automatically detects HTTP or UDP V3
- 📡 UDP V3 Support - Full support for UDP-only devices (commands 20 and 90). Tested on Revogi FW 5.11
- 🛡️ Robust Error Handling - Same retry logic and timeout handling for both protocols
- 🎯 Simplified Usage - Same methods (
turn_on,turn_off,get_data) work regardless of device protocol - ⚡ Enhanced Performance - Optimized command routing and reduced overhead
🔧 Supported Hardware
Firmware Compatibility & Testing Status
Device Models
- Max Hauri MaxSmart Power Station (6 ports)
- Max Hauri MaxSmart Smart Plug (1 port)
- Revogi Smart Power Strip (SOW323)
- CoCoSo Smart Power Strip (SOW323)
- Extel Soky Power Strip
- MCL DOM-PPS06I
🤝 Help expand compatibility! If you have Revogi-based devices with different firmware versions, please test them and let us know - we'd love to add them to the supported list!
Data Format Detection
The module automatically detects your device's data format:
- String floats ("5.20") → Direct watt values
- Integer milliwatts (5200) → Converted to watts (×0.001)
- Integer watts (5) → Direct watt values
📦 Installation
From PyPI
pip install maxsmart
From Source
git clone https://github.com/superkikim/maxsmart.git
cd maxsmart
pip install .
Dependencies
- Python 3.7+
- aiohttp (async HTTP client)
- getmac (MAC address discovery)
- matplotlib (for example scripts)
🚀 Quick Start
import asyncio
from maxsmart import MaxSmartDiscovery, MaxSmartDevice
async def main():
# Discover devices on network
devices = await MaxSmartDiscovery.discover_maxsmart()
if not devices:
print("No devices found")
return
# Connect to first device
device = MaxSmartDevice(devices[0]['ip'])
await device.initialize_device()
print(f"Connected to {device.name} (FW: {device.version})")
print(f"Serial: {devices[0]['sn']}")
print(f"CPU ID: {devices[0]['cpuid']}")
# Control ports
await device.turn_on(1) # Turn on port 1
state = await device.check_state(1) # Check port 1 state
power = await device.get_power_data(1) # Get power consumption
print(f"Port 1: {'ON' if state else 'OFF'}, {power['watt']}W")
# Cleanup
await device.close()
asyncio.run(main())
🔄 Protocol Transparency
Unified API for All Devices
The module automatically detects and supports both HTTP and UDP V3 protocols transparently:
# Same code works for both HTTP and UDP V3 devices!
device = MaxSmartDevice('192.168.1.100') # Auto-detects protocol
await device.initialize_device()
# These methods work regardless of protocol:
await device.turn_on(1) # ✅ HTTP or UDP V3
await device.turn_off(3) # ✅ HTTP or UDP V3
state = await device.check_state(1) # ✅ HTTP or UDP V3
power = await device.get_power_data(1) # ✅ HTTP or UDP V3
data = await device.get_data() # ✅ HTTP or UDP V3
print(f"Protocol: {device.protocol}") # Shows 'http' or 'udp_v3'
Firmware Support Matrix
| Firmware Version | HTTP Support | UDP V3 Support | Auto-Detection Result | Recommended |
|---|---|---|---|---|
| 1.30 | ✅ Full | ❌ None | HTTP |
HTTP (only option) |
| 2.11 | ✅ Full | ⚠️ Partial* | HTTP |
HTTP (more features) |
| 5.xx+ | ❌ None | ✅ Full | UDP V3 |
UDP V3 (only option) |
*Firmware 2.11 supports UDP V3 commands (20, 90) but with limited data response
Protocol Support Matrix
| Feature | HTTP Devices | UDP V3 Devices | Notes |
|---|---|---|---|
| Port Control | ✅ | ✅ | turn_on(), turn_off() |
| State Checking | ✅ | ✅ | check_state(), get_data() |
| Power Monitoring | ✅ | ✅ | get_power_data() |
| Statistics | ✅ | ❌ | get_statistics() HTTP only |
| Port Naming | ✅ | ❌ | change_port_name() HTTP only |
| Device Time | ✅ | ❌ | get_device_time() HTTP only |
Manual Protocol Selection
# Force specific protocol if needed
device_http = MaxSmartDevice('192.168.1.100', protocol='http')
device_udp = MaxSmartDevice('192.168.1.101', protocol='udp_v3')
# Auto-detection (recommended)
device_auto = MaxSmartDevice('192.168.1.102') # protocol=None
🔍 Device Discovery
Network Scanning
from maxsmart import MaxSmartDiscovery
# Discover all devices on local network (simplified format)
discovery = MaxSmartDiscovery()
devices = await discovery.discover_maxsmart()
for device in devices:
print(f"Name: {device['name']}")
print(f"IP: {device['ip']}")
print(f"Serial: {device['sn']}")
print(f"Firmware: {device['ver']}")
print(f"MAC: {device.get('mac', 'N/A')}")
print(f"Protocol: {device.get('protocol', 'unknown')}")
print(f"Ports: {device.get('nr_of_ports', 6)}")
Essential Discovery Format
The module now returns a clean, essential format:
{
"sn": "SWP6023002003697", # Serial number (primary identifier)
"name": "Living Room Strip", # Device name
"pname": ["TV", "Lamp", ...], # Port names (empty for newer firmware)
"ip": "192.168.1.100", # IP address
"ver": "1.30", # Firmware version
"mac": "AA:BB:CC:DD:EE:FF", # MAC address via ARP
"server": "www.maxsmart.ch" # Cloud server endpoint
}
Targeted Discovery
# Query specific IP address
devices = await MaxSmartDiscovery.discover_maxsmart(ip="192.168.1.100")
# With custom timeout and locale
devices = await MaxSmartDiscovery.discover_maxsmart(
ip="192.168.1.100",
user_locale="fr",
timeout=5.0
)
🆔 Device Identification
Network Identifiers
device = MaxSmartDevice('192.168.1.100')
await device.initialize_device()
# Get MAC address via ARP (best effort)
mac_address = await device.get_mac_address_via_arp()
print(f"ARP MAC: {mac_address}")
Identification Strategy
The module uses serial number as primary identifier with hardware identifiers for additional validation:
- Serial Number (primary) - Device serial from UDP discovery
- CPU ID (validation) - Hardware-based unique identifier
- MAC Address (network) - Network hardware identifier via ARP
🎛️ Device Control
Basic Port Operations
device = MaxSmartDevice('192.168.1.100')
await device.initialize_device()
# Individual ports (1-6)
await device.turn_on(1) # Turn on port 1
await device.turn_off(3) # Turn off port 3
# Master control (port 0 = all ports)
await device.turn_on(0) # Turn on all ports
await device.turn_off(0) # Turn off all ports
# State checking
port1_state = await device.check_state(1) # Single port: 0 or 1
all_states = await device.check_state() # All ports: [0,1,0,1,1,0]
Device Information
# Device properties (available after initialization)
print(f"Device name: {device.name}")
print(f"IP address: {device.ip}")
print(f"Serial number: {device.sn}")
print(f"Firmware version: {device.version}")
print(f"Strip name: {device.strip_name}")
print(f"Port names: {device.port_names}")
Port Name Management
# Retrieve current port names
port_mapping = await device.retrieve_port_names()
print(port_mapping)
# Output: {'Port 0': 'Living Room Strip', 'Port 1': 'TV', 'Port 2': 'Lamp', ...}
# Change port names (firmware v1.30 only)
try:
await device.change_port_name(1, "Smart TV")
await device.change_port_name(0, "Entertainment Center") # Strip name
print("Port names updated successfully")
except FirmwareError as e:
print(f"Port renaming not supported: {e}")
⚡ Advanced Polling & Monitoring
Adaptive Polling System
# Start intelligent polling (mimics official app)
await device.start_adaptive_polling()
# Polling behavior:
# - Normal: polls every 5 seconds
# - Burst: polls every 2 seconds for 4 cycles after commands
# - Auto-triggers burst mode on turn_on/turn_off operations
# Register callback for poll events
def poll_handler(poll_data):
mode = poll_data['mode'] # 'normal' or 'burst'
count = poll_data['poll_count'] # Poll sequence number
data = poll_data['device_data'] # Switch states and watt values
print(f"Poll #{count} ({mode}): {data}")
device.register_poll_callback("monitor", poll_handler)
# Commands automatically trigger burst mode
await device.turn_on(1) # Triggers 2-second polling for 8 seconds
# Stop polling
await device.stop_adaptive_polling()
Real-time Change Detection
async def on_consumption_change(data):
"""Called when power consumption changes >1W"""
port = data['port']
port_name = data['port_name']
change = data['change']
current = data['current_watt']
print(f"{port_name} (Port {port}): {current:.1f}W ({change:+.1f}W)")
async def on_state_change(data):
"""Called when port switches on/off"""
port = data['port']
port_name = data['port_name']
state = data['state_text'] # 'ON' or 'OFF'
print(f"{port_name} (Port {port}): {state}")
# Setup monitoring with automatic change detection
await device.setup_realtime_monitoring(
consumption_callback=on_consumption_change,
state_callback=on_state_change
)
# Start polling to enable monitoring
await device.start_adaptive_polling()
📊 Power Monitoring
Real-time Data
# Get current power consumption for specific port
power_data = await device.get_power_data(1)
print(f"Port 1: {power_data['watt']}W")
# Get comprehensive device data
all_data = await device.get_data()
print(f"Switch states: {all_data['switch']}") # [0,1,0,1,1,0]
print(f"Power values: {all_data['watt']}") # [0.0,15.2,0.0,8.7,122.5,0.0]
Historical Statistics
# Statistics types:
# 0 = Hourly (last 24 hours)
# 1 = Daily (last 30 days)
# 2 = Monthly (last 12 months)
# Get hourly data for all ports combined
hourly_stats = await device.get_statistics(0, 0) # port 0, type 0
print(f"Last 24 hours: {hourly_stats['watt']}")
print(f"Period ending: {hourly_stats['year']}-{hourly_stats['month']}-{hourly_stats['day']} {hourly_stats['hour']}:00")
# Get daily data for specific port
daily_stats = await device.get_statistics(1, 1) # port 1, type 1
print(f"Port 1 daily consumption: {daily_stats['watt']}")
# Statistics include cost information if configured
if daily_stats['cost'] > 0:
print(f"Estimated cost: {daily_stats['cost']} {daily_stats['currency']}/kWh")
Data Visualization
# The example scripts include comprehensive visualization
# See: example_scripts/maxsmart_tests_async.py
import matplotlib.pyplot as plt
# Get monthly data
monthly_data = await device.get_statistics(0, 2)
watt_values = monthly_data['watt']
# Plot consumption over time
plt.figure(figsize=(12, 6))
plt.plot(watt_values)
plt.title('Monthly Power Consumption')
plt.ylabel('Power (W)')
plt.xlabel('Month')
plt.show()
🕐 Device Time Management
# Get device's real-time clock
time_data = await device.get_device_time()
print(f"Device time: {time_data['time']}")
# Output: "2024-07-17,14:30:25"
🔧 Session Management & Health Checks
Context Manager Usage
# Automatic initialization and cleanup
async with MaxSmartDevice('192.168.1.100') as device:
await device.turn_on(1)
power = await device.get_power_data(1)
print(f"Power: {power['watt']}W")
# Device automatically closed on exit
Health Monitoring
# Check device connectivity and performance
health = await device.health_check()
print(f"Status: {health['status']}")
print(f"Response time: {health['response_time']}ms")
print(f"Polling active: {health['polling_active']}")
if health['status'] == 'unhealthy':
print(f"Error: {health['error']}")
Manual Cleanup
# Always clean up resources
try:
# ... device operations
pass
finally:
await device.close() # Stops polling and closes HTTP session
🛠️ Error Handling
Exception Types
from maxsmart.exceptions import (
DiscoveryError, # Device discovery failures
ConnectionError, # Network connectivity issues
CommandError, # Command execution failures
StateError, # Device state inconsistencies
FirmwareError, # Firmware compatibility issues
DeviceTimeoutError # Device response timeouts
)
try:
devices = await MaxSmartDiscovery.discover_maxsmart()
device = MaxSmartDevice(devices[0]['ip'])
await device.initialize_device()
await device.turn_on(1)
except DiscoveryError as e:
print(f"Discovery failed: {e}")
except ConnectionError as e:
print(f"Network error: {e}")
except FirmwareError as e:
print(f"Firmware limitation: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
Retry Logic
The module includes built-in retry mechanisms:
- Discovery: 3 attempts with exponential backoff
- Commands: 3 retries with 1-second delays
- State verification: 3 verification attempts
- Session management: Automatic reconnection on failures
📁 Example Scripts
The example_scripts/ directory contains comprehensive examples:
Basic Examples
test_discovery_async.py- Device discovery with validationtest_device_identification.py- Hardware identification testingget_port_names_async.py- Port name retrieval and displayretrieve_states_async.py- Port state checkingshow_consumption.py- Real-time power monitoring
Advanced Examples
maxsmart_tests_async.py- Complete feature testing with visualizationtest_adaptive_polling.py- Polling system demonstrationrename_ports.py- Port name management (v1.30 firmware)test_device_timestamps.py- Device time synchronization
Running Examples
# Install visualization dependencies
pip install -r example_scripts/requirements.txt
# Run comprehensive test suite
python example_scripts/maxsmart_tests_async.py
# Test device identification
python example_scripts/test_device_identification.py
# Test adaptive polling
python example_scripts/test_adaptive_polling.py
# Monitor real-time consumption
python example_scripts/show_consumption.py
⚙️ Configuration Options
Discovery Configuration
# Custom timeout and retry settings
devices = await MaxSmartDiscovery.discover_maxsmart(
ip=None, # None for broadcast, IP for unicast
user_locale="en", # Locale for error messages
timeout=3.0, # Discovery timeout in seconds
max_attempts=3 # Maximum discovery attempts
)
Device Configuration
device = MaxSmartDevice(
ip_address="192.168.1.100",
auto_polling=False # Start polling automatically after init
)
# Custom session timeouts
device.DEFAULT_TIMEOUT = 15.0 # Command timeout
device.RETRY_COUNT = 5 # Command retry attempts
Polling Configuration
# Customize polling intervals
device.NORMAL_INTERVAL = 10.0 # Normal polling interval (default: 5s)
device.BURST_INTERVAL = 1.0 # Burst polling interval (default: 2s)
device.BURST_CYCLES = 6 # Burst cycle count (default: 4)
🔒 Security Considerations
Network Security
- Unencrypted HTTP: All communication is in plain text
- No authentication: Devices don't require credentials
- Local network only: Devices must be on same network as client
- Trusted networks: Only use on secure, trusted networks
Best Practices
# Use connection limits to prevent resource exhaustion
device.DEFAULT_CONNECTOR_LIMIT = 10
device.DEFAULT_CONNECTOR_LIMIT_PER_HOST = 5
# Always clean up resources
async with MaxSmartDevice(ip) as device:
# ... operations
pass # Automatic cleanup
# Handle exceptions gracefully
try:
await device.turn_on(1)
except Exception as e:
logging.error(f"Command failed: {e}")
🐛 Troubleshooting
Common Issues
Device not found during discovery
# Try targeted discovery
devices = await MaxSmartDiscovery.discover_maxsmart(ip="192.168.1.100")
# Check network connectivity
import socket
try:
socket.create_connection(("192.168.1.100", 8888), timeout=3)
print("Device reachable")
except:
print("Device unreachable")
Power scaling issues
# Check device firmware and data format
await device.initialize_device()
print(f"Firmware: {device.version}")
print(f"Data format: {device._watt_format}")
print(f"Conversion factor: {device._watt_multiplier}")
# Test with raw data
raw_data = await device.get_data()
print(f"Raw power values: {raw_data['watt']}")
Firmware compatibility errors
# Check device firmware version
await device.initialize_device()
print(f"Firmware: {device.version}")
print(f"Data format: {device._watt_format}")
# Firmware capabilities:
# v1.06, v1.10, v1.30: Full support including port renaming
# v2.11, v3.36, v3.49: Full control, basic port renaming
Connection timeouts
# Increase timeouts for slow networks
device.DEFAULT_TIMEOUT = 30.0
device.SESSION_TIMEOUT = 60.0
# Reduce retry count to fail faster
device.RETRY_COUNT = 1
Debug Logging
import logging
logging.basicConfig(level=logging.DEBUG)
# Enable detailed HTTP logging
logging.getLogger('aiohttp').setLevel(logging.DEBUG)
📈 Performance Notes
Efficient Usage
- Reuse device instances: Don't recreate for each operation
- Use adaptive polling: More efficient than manual polling
- Batch operations: Minimize individual command calls
- Context managers: Ensure proper cleanup
Resource Management
# Good: Reuse device instance
device = MaxSmartDevice(ip)
await device.initialize_device()
for port in range(1, 7):
await device.turn_on(port)
await device.close()
# Bad: Create new instances
for port in range(1, 7):
device = MaxSmartDevice(ip) # Inefficient
await device.initialize_device()
await device.turn_on(port)
await device.close()
🤝 Credits
This module builds upon reverse engineering work by @altery who documented the MaxSmart communication protocol.
📄 License
MIT License - see LICENSE file for details.
🔗 Links
- GitHub Repository: https://github.com/superkikim/maxsmart
- PyPI Package: https://pypi.org/project/maxsmart/
- Issues & Support: https://github.com/superkikim/maxsmart/issues
- Example Scripts: https://github.com/superkikim/maxsmart/tree/main/example_scripts
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 maxsmart-2.1.0.tar.gz.
File metadata
- Download URL: maxsmart-2.1.0.tar.gz
- Upload date:
- Size: 44.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3800c7392ab76aeef120fb8144b2de2afd6251077d24d531481e322c31b4d79a
|
|
| MD5 |
d6beb27dce44510402b5bc71500f472e
|
|
| BLAKE2b-256 |
e8525e764f9fad45dca82b881581e7ae87f524c345348289380971b9655d650d
|
Provenance
The following attestation bundles were made for maxsmart-2.1.0.tar.gz:
Publisher:
release.yml on Superkikim/maxsmart
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
maxsmart-2.1.0.tar.gz -
Subject digest:
3800c7392ab76aeef120fb8144b2de2afd6251077d24d531481e322c31b4d79a - Sigstore transparency entry: 381098712
- Sigstore integration time:
-
Permalink:
Superkikim/maxsmart@040caf848f764377cf430888c4b9fb331f44f4c3 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/Superkikim
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@040caf848f764377cf430888c4b9fb331f44f4c3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file maxsmart-2.1.0-py3-none-any.whl.
File metadata
- Download URL: maxsmart-2.1.0-py3-none-any.whl
- Upload date:
- Size: 43.5 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 |
7ffcd0dc77b8bbc5db257b4280a82f7b0140c31b087d33d7d148fd38ebd76b20
|
|
| MD5 |
bc2392b13469ec89b035ed97b264f6a9
|
|
| BLAKE2b-256 |
07ff038fed66daab223ba8d8753b2267a888ae8aea65ef413c3de3baf9196a8b
|
Provenance
The following attestation bundles were made for maxsmart-2.1.0-py3-none-any.whl:
Publisher:
release.yml on Superkikim/maxsmart
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
maxsmart-2.1.0-py3-none-any.whl -
Subject digest:
7ffcd0dc77b8bbc5db257b4280a82f7b0140c31b087d33d7d148fd38ebd76b20 - Sigstore transparency entry: 381098729
- Sigstore integration time:
-
Permalink:
Superkikim/maxsmart@040caf848f764377cf430888c4b9fb331f44f4c3 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/Superkikim
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@040caf848f764377cf430888c4b9fb331f44f4c3 -
Trigger Event:
release
-
Statement type: