Python library for direct TCP control of Duosida EV wall chargers, bypassing the cloud API for local control and monitoring.
- Network Discovery: Find Duosida chargers on your local network
- Real-time Telemetry: Read voltage, current, power, temperature, and energy consumption
- Current Control: Set maximum charging current (6-32A)
- Start/Stop Charging: Remote control to start or stop charging sessions
- Configuration Settings: Connection timeout, max/min voltage, max temperature, direct work mode
- JSON Export: Get telemetry data as JSON for integration with other tools
- No Cloud Required: Direct TCP communication with the charger
pip install duosida-evOr install from source:
git clone https://github.com/americodias/duosida-ev.git
cd duosida-ev
pip install -e .from duosida_ev import discover_chargers
devices = discover_chargers()
for device in devices:
print(f"Found: {device['ip']}")
print(f" Device ID: {device['device_id']}")
print(f" MAC: {device['mac']}")from duosida_ev import DuosidaCharger
charger = DuosidaCharger(
host="192.168.1.100",
device_id="YOUR_DEVICE_ID"
)
charger.connect()
status = charger.get_status()
print(f"Voltage: {status.voltage}V")
print(f"Current: {status.current}A")
print(f"Power: {status.power}W")
print(f"State: {status.state}")
charger.disconnect()charger.connect()
charger.set_max_current(16) # Set to 16A
charger.disconnect()Note: All configuration methods are write-only. The charger does not return configuration values in its status response. You must track configuration settings externally if needed.
charger.connect()
# Connection timeout (30-900 seconds)
charger.set_connection_timeout(120)
# Temperature limits (85-95°C)
charger.set_max_temperature(90)
# Voltage limits
charger.set_max_voltage(280) # 265-290V
charger.set_min_voltage(90) # 70-110V
# Direct work mode (plug and charge)
charger.set_direct_work_mode(True) # Enable
charger.set_direct_work_mode(False) # Disable
# LED brightness (0=off, 1=low, 3=high)
charger.set_led_brightness(3)
# Stop on EV disconnect
charger.set_stop_on_disconnect(True) # Enable auto-stop
# Generic config (any key/value)
charger.set_config("VendorMaxWorkCurrent", "16")
charger.disconnect()charger.connect()
# Start charging
charger.start_charging()
# Stop charging
charger.stop_charging()
charger.disconnect()import json
charger.connect()
status = charger.get_status()
# Convert to dictionary
data = status.to_dict()
print(json.dumps(data, indent=2))
# Access individual fields
print(f"CP Voltage: {status.cp_voltage}V")
print(f"State: {status.state}")
charger.disconnect()def on_status(status):
print(f"Power: {status.power}W")
charger.connect()
charger.monitor(interval=2.0, callback=on_status)
charger.disconnect()# Discover chargers on the network
duosida discover
# Get charger status
duosida status --host 192.168.1.100 --device-id YOUR_DEVICE_ID
# Get status in JSON format
duosida status --host 192.168.1.100 --device-id YOUR_DEVICE_ID --json
# Set maximum current
duosida set-current --host 192.168.1.100 --device-id YOUR_DEVICE_ID 16
# Start charging
duosida start --host 192.168.1.100 --device-id YOUR_DEVICE_ID
# Stop charging
duosida stop --host 192.168.1.100 --device-id YOUR_DEVICE_ID
# Monitor continuously
duosida monitor --host 192.168.1.100 --device-id YOUR_DEVICE_ID
# Configuration commands (host only - device ID auto-discovered)
duosida set-timeout --host 192.168.1.100 120 # 30-900 seconds
duosida set-max-temp --host 192.168.1.100 90 # 85-95°C
duosida set-max-voltage --host 192.168.1.100 280 # 265-290V
duosida set-min-voltage --host 192.168.1.100 90 # 70-110V
duosida set-direct-mode --host 192.168.1.100 on # on/off
duosida set-led-brightness --host 192.168.1.100 3 # 0=off, 1=low, 3=high| Field | Description |
|---|---|
conn_status |
Connection status code (0-6) |
cp_voltage |
Control Pilot voltage (V) |
state |
Human-readable status (Available, Charging, Finished, etc.) |
voltage |
Line voltage L1 (V) |
voltage_l2 |
Line voltage L2 (V) |
voltage_l3 |
Line voltage L3 (V) |
current |
Charging current L1 (A) |
current_l2 |
Charging current L2 (A) |
current_l3 |
Charging current L3 (A) |
power |
Power consumption (W) |
temperature_station |
Station temperature (°C) |
session_energy |
Current session energy (kWh) |
session_time |
Session duration (minutes) |
device_id |
Device identifier |
model |
Device model |
manufacturer |
Manufacturer |
firmware |
Firmware version |
| Code | State |
|---|---|
| 0 | Available |
| 1 | Preparing |
| 2 | Charging |
| 3 | Cooling |
| 4 | SuspendedEV |
| 5 | Finished |
| 6 | Holiday |
The device ID can be found:
- On the QR code label on the left side of the charger
- Using the
duosida discovercommand (when on the same network) - In the official Duosida mobile app
- In Home Assistant integration settings
- Python 3.6+
- No external dependencies (uses only standard library)
- Port: 9988 (TCP)
- Message Format: Protobuf
- Discovery: UDP broadcast on port 48890/48899
See Settings Reference for detailed documentation on charger configuration options (Direct Work Mode, Level Detection, etc.).
To capture traffic between the Duosida app and charger for reverse engineering, you can use Wireshark on a computer connected to the same network, or use tcpdump on your router if it supports SSH access.
- Install Wireshark
- Connect your computer to the same network as the charger
- Start capture with filter:
host <charger-ip> and tcp port 9988 - Use the Duosida app to interact with the charger
- Stop capture and save the
.pcapfile
If your router supports SSH (OpenWrt, DD-WRT, etc.):
ssh root@<router-ip>
# Capture traffic to/from charger
tcpdump -i br-lan -w /tmp/charger_capture.pcap \
'host <charger-ip> and tcp port 9988'Download the capture:
scp root@<router-ip>:/tmp/charger_capture.pcap ./charger_capture.pcapUniFi Dream Machine Pro specific instructions
For UDM Pro with separate access points, you need to SSH into the Access Point where the phone or charger is connected (not the UDM Pro itself, since local traffic may not pass through the router):
# SSH into the Access Point (not the UDM Pro)
ssh root@<access-point-ip>
# Capture on the wireless interface (ra2) for your IoT VLAN
tcpdump -i ra2 -w /tmp/charger_capture.pcap \
'host 192.168.1.100 and host 192.168.1.50 and tcp port 9988'Options:
-i ra2- Interface for 2.4GHz/5GHz AP (wireless)host 192.168.1.100- Charger IPhost 192.168.1.50- Phone IP (update as needed)
Alternative - capture everything for 30 seconds:
timeout 30 tcpdump -i ra2 -w /tmp/charger_full.pcap \
'host 192.168.1.100 and tcp port 9988'Download the capture from the AP:
scp root@<access-point-ip>:/tmp/charger_capture.pcap ./charger_capture.pcapMIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
This library was created by reverse-engineering the TCP communication between the official Duosida app (orange icon, local control) and the charger. Note that Duosida has two apps - the orange one uses direct local communication while the blue one uses the cloud API.
The reverse engineering and code development was mostly done using Claude Code and Genspark.
References:
- Home Assistant Duosida integration - Cloud API integration, used as reference for status codes and feature identification