Async Python library for Fronius Wattpilot Wallbox devices.
Connects to Wattpilot EV chargers over WebSocket using a reverse-engineered API (the same protocol used by the official Wattpilot.Solar mobile app). Provides real-time property synchronization, an MQTT bridge, Home Assistant discovery, and an interactive CLI shell.
- Fully async — built on
asyncioandwebsockets, compatible with Home Assistant - All Wattpilot hardware — supports standard (V2) and Flex models via PBKDF2 and bcrypt authentication
- 388+ properties — access every charger property in real time
- MQTT bridge — publish properties and subscribe to commands via
aiomqtt - Home Assistant discovery — automatic entity creation with proper device classes
- Interactive shell — async CLI with TAB completion via
prompt_toolkit - Type-safe — full type hints,
mypy --strict, PEP 561py.typedmarker - 100% test coverage — 241 unit tests + 18 integration tests
- Python 3.12+
- Local network access to a Fronius Wattpilot charger
pip install wattpilot-apiFor development:
pip install -e ".[dev]"import asyncio
from wattpilot_api import Wattpilot, LoadMode
async def main():
async with Wattpilot("192.168.1.100", "your_password") as wp:
# Read properties
print(f"Serial: {wp.serial}")
print(f"Firmware: {wp.firmware}")
print(f"Power: {wp.power:.2f} kW")
print(f"Amperage: {wp.amp} A")
print(f"Car connected: {wp.car_connected}")
print(f"Mode: {LoadMode(wp.mode).name}")
# Set charging amperage
await wp.set_power(16)
# Set charging mode
await wp.set_mode(LoadMode.ECO)
# Register a callback for property changes
def on_change(name, value):
print(f"{name} = {value}")
unsub = wp.on_property_change(on_change)
# Access all 388+ raw properties
for key, value in sorted(wp.all_properties.items()):
print(f" {key} = {value}")
unsub() # unsubscribe when done
asyncio.run(main())export WATTPILOT_HOST=192.168.1.100
export WATTPILOT_PASSWORD=your_password
wattpilotshellexport WATTPILOT_HOST=192.168.1.100
export WATTPILOT_PASSWORD=your_password
export MQTT_ENABLED=true
export MQTT_HOST=your_mqtt_broker
export HA_ENABLED=true
wattpilotshell| Device | Auth | Status |
|---|---|---|
| Wattpilot Home (V2) | PBKDF2 | Tested |
| Wattpilot Home FLEX 11 C6 | bcrypt | Supported |
| Wattpilot Home FLEX 22kW | bcrypt | Supported |
| Wattpilot (cloud via Fronius API) | PBKDF2 | Supported |
Authentication is auto-detected:
- If the
authRequiredmessage includes ahashfield, that algorithm is used - Otherwise, if
devicetypeiswattpilot_flex, bcrypt is used - Otherwise, PBKDF2 is used (default)
from wattpilot_api import Wattpilot
# Constructor
wp = Wattpilot(
host="192.168.1.100",
password="your_password",
serial=None, # auto-detected from device
cloud=False, # True for Fronius cloud connection
connect_timeout=30.0, # seconds
init_timeout=30.0, # seconds
command_timeout=10.0, # seconds to wait for a command acknowledgement
)
# Connect / disconnect
await wp.connect()
await wp.disconnect()
# Or use as async context manager
async with Wattpilot("192.168.1.100", "password") as wp:
...| Property | Type | Description |
|---|---|---|
wp.connected |
bool |
Connection status |
wp.serial |
str |
Device serial number |
wp.name |
str |
Device name |
wp.manufacturer |
str |
Manufacturer ("fronius") |
wp.device_type |
str |
Device type ("wattpilot_V2", "wattpilot_flex") |
wp.version |
str |
Firmware version |
wp.amp |
int |
Current amperage setting |
wp.mode |
int |
Charging mode (3=Default, 4=Eco, 5=NextTrip) |
wp.car_connected |
int |
Car status (1=No car, 2=Charging, 3=Ready, 4=Complete) |
wp.allow_charging |
bool |
Whether charging is allowed |
wp.voltage1/2/3 |
float |
Phase voltages |
wp.amps1/2/3 |
float |
Phase currents |
wp.power1/2/3 |
float |
Phase power (kW) |
wp.power |
float |
Total power (kW) |
wp.frequency |
float |
Grid frequency (Hz) |
wp.energy_counter_since_start |
float |
Session energy (Wh) |
wp.energy_counter_total |
float |
Lifetime energy (Wh) |
wp.error_state |
int |
Error code |
wp.cable_type |
int |
Cable capacity (A) |
wp.cable_lock |
int |
Lock mode |
wp.access_state |
int |
Access state |
wp.phases |
list |
Phase configuration |
wp.all_properties |
dict |
All 388+ raw properties |
await wp.set_power(16) # Set amperage (6-32)
await wp.set_mode(LoadMode.ECO) # Set charging mode
await wp.set_property("fna", "MyCharger") # Set any writable propertyCommands wait for the charger's acknowledgement. They raise CommandError when the
charger rejects the value (for example a read-only key), CommandTimeoutError when no
acknowledgement arrives within command_timeout, and ConnectionError when the
connection closes first.
# Sync or async callbacks supported
unsub = wp.on_property_change(lambda name, value: print(f"{name}={value}"))
unsub() # unsubscribe
unsub = wp.on_message(lambda msg: print(msg["type"]))
unsub()Async on_message callbacks run inline in the receive loop. Do not await a command
(such as set_property) inside one: its acknowledgement cannot arrive while the loop is
blocked, so the command times out. Schedule it with asyncio.create_task() instead.
from wattpilot_api import LoadMode, CarStatus, ErrorState, AccessState, CableLockMode
LoadMode(wp.mode) # LoadMode.DEFAULT / ECO / NEXTTRIP
CarStatus(wp.car_connected) # CarStatus.NO_CAR / CHARGING / READY / COMPLETEfrom wattpilot_api import (
AuthenticationError,
CommandError,
CommandTimeoutError,
ConnectionError,
WattpilotError,
)
try:
await wp.connect()
except AuthenticationError:
print("Device or local credentials rejected authentication")
except ConnectionError:
print("Cannot reach device or connection lost during handshake")
try:
await wp.set_property("amp", 16)
except CommandTimeoutError:
print("The charger did not acknowledge the command in time")
except CommandError as err:
print(f"The charger rejected the command: {err}")The MqttBridge class publishes property changes to MQTT and subscribes to set commands.
from wattpilot_api import Wattpilot, MqttBridge, MqttConfig
from wattpilot_api.definition import load_api_definition
api_def = load_api_definition()
config = MqttConfig(host="localhost", port=1883)
async with Wattpilot("192.168.1.100", "password") as wp:
bridge = MqttBridge(wp, config, api_def)
await bridge.start()
# ... bridge publishes property changes to MQTT
await bridge.stop()from wattpilot_api import HomeAssistantDiscovery, HaConfig
ha = HomeAssistantDiscovery(wp, bridge, HaConfig(enabled=True), api_def)
await ha.discover_all()
await ha.publish_initial_values()# Install with dev dependencies
pip install -e ".[dev]"
# Run tests (100% coverage required)
pytest --cov=wattpilot_api --cov-report=term-missing --cov-fail-under=100
# Run integration tests against a real device
WATTPILOT_HOST=192.168.1.100 WATTPILOT_PASSWORD=password pytest -m integration -v
# Lint and type check
ruff check src/ tests/
mypy src/wattpilot_api/
# Pre-commit (runs ruff, ruff-format, mypy)
pre-commit run --all-filessrc/wattpilot_api/
├── __init__.py # Public API exports
├── client.py # Async WebSocket client
├── auth.py # PBKDF2 + bcrypt authentication
├── models.py # Enums and dataclasses
├── exceptions.py # Exception hierarchy
├── definition.py # YAML property metadata loader
├── mqtt.py # Async MQTT bridge
├── discovery.py # Home Assistant MQTT discovery
├── shell.py # Async CLI shell
├── py.typed # PEP 561 marker
└── resources/
└── wattpilot.yaml # Property definitions (3300+ lines)
The WebSocket API has been reverse-engineered from multiple sources:
See CHANGELOG.md for version history, migration guide, and upstream issue fixes.
Apache License 2.0 — see LICENSE for details. Earlier releases were MIT licensed; see NOTICE.
This project uses a reverse-engineered API and is not officially supported by Fronius. Use at your own risk.