Skip to main content

Indevolt API

Python client library for communicating with Indevolt devices (home battery systems).

Features

  • Async/await support using aiohttp
  • Fully typed with type hints
  • Simple and intuitive API
  • Comprehensive error handling

Installation

pip install indevolt-api

Quick Start

import asyncio
import aiohttp
from indevolt_api import (
    IndevoltAPI,
    IndevoltConfig,
    IndevoltEnergyMode,
    IndevoltSystem,
    SET_REALTIME_ACTION,
    IndevoltRealtimeAction,
)

async def main():
    async with aiohttp.ClientSession() as session:
        api = IndevoltAPI(host="192.168.1.100", port=8080, session=session)

        # Get device configuration
        config = await api.get_config()
        print(f"Device config: {config}")

        # Fetch data using StrEnum members — response keys are the same strings
        data = await api.fetch_data([IndevoltConfig.READ_ENERGY_MODE, IndevoltSystem.INPUT_POWER])
        print(f"Energy mode: {data[IndevoltConfig.READ_ENERGY_MODE]}")
        print(f"Input power: {data[IndevoltSystem.INPUT_POWER]}")

        # Write a single value
        await api.set_data(IndevoltConfig.WRITE_DISCHARGE_LIMIT, 50)

        # Write a real-time charge command
        await api.set_data(SET_REALTIME_ACTION, [IndevoltRealtimeAction.CHARGE, 700, 80])

asyncio.run(main())

Device Discovery

The library supports two complementary discovery mechanisms.

Active Discovery

Sends a UDP broadcast and collects replies from devices on the same network. Use async_discover() when you need devices immediately at startup.

import asyncio
import aiohttp
from indevolt_api import async_discover, IndevoltAPI

async def main():
    # Broadcast AT+IGDEVICEIP and wait for replies (default: 5 s)
    devices = await async_discover()

    if not devices:
        print("No devices found")
        return

    print(f"Found {len(devices)} device(s):")
    for device in devices:
        print(f"  - {device.host}:{device.port} (name: {device.name})")

    # Connect to the first discovered device
    async with aiohttp.ClientSession() as session:
        api = IndevoltAPI.from_discovered_device(devices[0], session)
        config = await api.get_config()
        print(f"Device config: {config}")

asyncio.run(main())

How it works:

  1. Sends ACTIVE_DISCOVERY_MESSAGE (AT+IGDEVICEIP) via UDP broadcast to 255.255.255.255:8099
  2. Devices respond to local port ACTIVE_DISCOVERY_PORT (10000) with their IP and optional metadata
  3. Returns a list of DiscoveredDevice objects

Note: UDP port 10000 must be available and not blocked by a firewall.

Passive Discovery

Listens for unsolicited broadcasts that devices emit on their own. Use PassiveDiscoveryProtocol for long-running applications (e.g. Home Assistant integrations) that need to detect devices as they appear without polling.

import asyncio
from indevolt_api import (
    PassiveDiscoveryProtocol,
    PASSIVE_DISCOVERY_PORT,
    PASSIVE_DISCOVERY_BIND_ADDR,
)

async def main():
    seen: set[str] = set()

    def on_device_discovered(host: str) -> None:
        if host not in seen:
            seen.add(host)
            print(f"Device announced itself: {host}")

    loop = asyncio.get_running_loop()
    transport, _ = await loop.create_datagram_endpoint(
        lambda: PassiveDiscoveryProtocol(on_device_discovered),
        local_addr=(PASSIVE_DISCOVERY_BIND_ADDR, PASSIVE_DISCOVERY_PORT),
    )

    try:
        await asyncio.Event().wait()  # run until cancelled
    finally:
        transport.close()

asyncio.run(main())

How it works:

  1. Devices periodically broadcast a BCF-D-prefixed UDP packet on port 8099
  2. PassiveDiscoveryProtocol filters packets by the PASSIVE_DISCOVERY_MAGIC prefix and invokes your callback with the sender's IP
  3. No outbound traffic is sent

Note: Bind to PASSIVE_DISCOVERY_BIND_ADDR (0.0.0.0) so the socket accepts broadcasts on all interfaces.

Discovery Examples

See examples/active_discovery_example.py and examples/passive_discovery_example.py for runnable examples.

API Reference

IndevoltAPI

__init__(host: str, port: int, session: aiohttp.ClientSession, timeout: float = 10.0)

Initialize the API client.

Parameters:

  • host (str): Device hostname or IP address
  • port (int): Device port number (typically 80 or 8080)
  • session (aiohttp.ClientSession): An aiohttp client session
  • timeout (float): Request timeout in seconds (default: 10.0)

Example:

# Default 10-second timeout (recommended for local devices)
api = IndevoltAPI(host="192.168.1.100", port=8080, session=session)

# Custom timeout
api = IndevoltAPI(host="192.168.1.100", port=8080, session=session, timeout=15.0)

classmethod from_discovered_device(device: DiscoveredDevice, session: aiohttp.ClientSession, timeout: float = 10.0)

Create an API client from a discovered device.

Parameters:

  • device (DiscoveredDevice): A device object returned by async_discover()
  • session (aiohttp.ClientSession): An aiohttp client session
  • timeout (float): Request timeout in seconds (default: 10.0)

Returns:

  • IndevoltAPI instance configured for the discovered device

Example:

devices = await async_discover()
if devices:
    api = IndevoltAPI.from_discovered_device(devices[0], session)

async fetch_data(t: str | list[str]) -> dict[str, Any]

Fetch data from the device.

Parameters:

  • t: A StrEnum member, a raw string key, or a list of either

Returns:

  • Dictionary whose keys are strings matching the requested cJson points. StrEnum members can be used directly to index the result.

Example:

from indevolt_api import IndevoltSystem, IndevoltGrid, IndevoltBattery

# Single point
data = await api.fetch_data(IndevoltBattery.SOC)
print(data[IndevoltBattery.SOC])

# Multiple points
data = await api.fetch_data([
    IndevoltSystem.INPUT_POWER,
    IndevoltSystem.OUTPUT_POWER,
    IndevoltGrid.VOLTAGE,
])
print(data[IndevoltSystem.INPUT_POWER])
print(data[IndevoltGrid.VOLTAGE])

async set_data(t: str | int, v: Any) -> bool

Write data to the device.

Parameters:

  • t: cJson point identifier (e.g., "47015" or 47015)
  • v: Value(s) to write (automatically converted to list of integers)

Returns:

  • True on success, False otherwise

Example:

from indevolt_api import IndevoltConfig, IndevoltEnergyMode, SET_REALTIME_ACTION, IndevoltRealtimeAction

# Single value
await api.set_data(IndevoltConfig.WRITE_DISCHARGE_LIMIT, 50)

# Real-time command with multiple values
await api.set_data(SET_REALTIME_ACTION, [IndevoltRealtimeAction.CHARGE, 700, 80])

# Set energy mode
await api.set_data(IndevoltConfig.WRITE_ENERGY_MODE, IndevoltEnergyMode.SELF_CONSUMED_PRIORITIZED)

async stop() -> bool

Stop any active real-time charge or discharge action.

Returns:

  • True on success, False if the command was rejected or a connection error occurred

Example:

succeeded = await api.stop()

async charge(power: int, target_soc: int) -> bool

Send a real-time charge command to the device.

Parameters:

  • power (int): Charge power in watts
  • target_soc (int): Target state of charge percentage

Returns:

  • True on success, False if the command was rejected or a connection error occurred

Example:

succeeded = await api.charge(power=700, target_soc=80)

async discharge(power: int, target_soc: int) -> bool

Send a real-time discharge command to the device.

Parameters:

  • power (int): Discharge power in watts
  • target_soc (int): Target state of charge percentage

Returns:

  • True on success, False if the command was rejected or a connection error occurred

Example:

succeeded = await api.discharge(power=400, target_soc=20)

async get_config() -> dict[str, Any]

Get system configuration from the device.

Returns:

  • Dictionary with device system configuration

Raises:

  • TimeoutError: If the request exceeds the configured timeout
  • aiohttp.ClientError: On network errors or non-200 HTTP responses

Example:

config = await api.get_config()
print(config)

check_charge_limits(power: int, target_soc: int, generation: int) -> None

Check that charge parameters do not exceed device limits. Raises an exception if any boundary is violated.

Parameters:

  • power (int): Requested charge power in watts
  • target_soc (int): Target state of charge percentage
  • generation (int): Device hardware generation (1 or 2), available from get_config() under device.generation

Raises:

  • PowerExceedsMaxError: If power exceeds the maximum for the given generation
  • SocBelowMinimumError: If target_soc is below the minimum SOC (5%)

Example:

config = await api.get_config()
generation = config["device"]["generation"]

try:
    api.check_charge_limits(power=1000, target_soc=80, generation=generation)
except PowerExceedsMaxError as e:
    print(f"Power {e.power}W exceeds max {e.max_power}W for gen {e.generation}")
except SocBelowMinimumError as e:
    print(f"Target SOC {e.target_soc}% is below minimum {e.minimum_soc}%")

check_discharge_limits(power: int, target_soc: int, generation: int) -> None

Check that discharge parameters do not exceed device limits. Raises an exception if any boundary is violated.

Parameters:

  • power (int): Requested discharge power in watts
  • target_soc (int): Target state of charge percentage
  • generation (int): Device hardware generation (1 or 2), available from get_config() under device.generation

Raises:

  • PowerExceedsMaxError: If power exceeds the maximum for the given generation
  • SocBelowMinimumError: If target_soc is below the minimum SOC (5%)

Example:

config = await api.get_config()
generation = config["device"]["generation"]

try:
    api.check_discharge_limits(power=600, target_soc=10, generation=generation)
except PowerExceedsMaxError as e:
    print(f"Power {e.power}W exceeds max {e.max_power}W for gen {e.generation}")
except SocBelowMinimumError as e:
    print(f"Target SOC {e.target_soc}% is below minimum {e.minimum_soc}%")

async_discover(timeout: float = 5.0) -> list[DiscoveredDevice]

Discover Indevolt devices on the local network using UDP broadcast.

Parameters:

  • timeout (float): Discovery timeout in seconds (default: 5.0)

Returns:

  • List of DiscoveredDevice objects representing found devices

Example:

devices = await async_discover(timeout=3.0)
for device in devices:
    print(f"Found: {device.host}:{device.port}")

DiscoveredDevice

Represents a discovered Indevolt device with the following attributes:

Attributes:

  • host (str): Device IP address
  • port (int): Device port number (default: 8080)
  • name (str | None): Device name if provided in discovery response
  • metadata (dict): Additional device information from discovery response

Example:

device = devices[0]
print(f"Device at {device.host}:{device.port}")
if device.name:
    print(f"Name: {device.name}")

Exception Handling

The library has two exception-handling behaviours depending on the method:

Methods that raise (fetch_data, get_config): network and HTTP errors propagate to the caller.

Methods that return False (set_data, stop, charge, discharge): all network, HTTP, and decoding errors are caught internally and logged at DEBUG level. These methods never raise on communication failure.

TimeoutError

Built-in Python exception raised by fetch_data and get_config when a request exceeds the configured timeout (default: 10 seconds). Not raised by set_data or the convenience helpers.

aiohttp.ClientError

Raised by fetch_data and get_config on network errors or non-200 HTTP responses. Not raised by set_data or the convenience helpers.

PowerExceedsMaxError

Raised by check_charge_limits() or check_discharge_limits() when the requested power exceeds the device maximum.

Attributes: power, max_power, generation

SocBelowMinimumError

Raised by check_charge_limits() or check_discharge_limits() when the target SOC is below the hard minimum of 5%.

Attributes: target_soc, minimum_soc

Example:

import aiohttp
from indevolt_api import IndevoltAPI

# fetch_data and get_config raise on error
try:
    data = await api.fetch_data("7101")
except TimeoutError:
    print("Request timed out")
except aiohttp.ClientError as e:
    print(f"Network/HTTP error: {e}")

# set_data and helpers return False on error — no try/except needed
succeeded = await api.set_data(IndevoltConfig.WRITE_DISCHARGE_LIMIT, 50)
if not succeeded:
    print("Command failed")

Note: You can adjust the timeout when creating the API client:

# Increase timeout if needed (e.g., for slower networks)
api = IndevoltAPI(host="192.168.1.100", port=8080, session=session, timeout=10.0)

Constants and Enums

All register keys and action values are available as typed StrEnum classes, importable directly from indevolt_api. Because StrEnum members are strings, they can be passed directly to fetch_data() and set_data(), and used to index the response dictionary — no manual conversion needed.

IndevoltConfig

Register keys for configurable device settings (read and write).

from indevolt_api import IndevoltConfig

# Write registers
IndevoltConfig.WRITE_ENERGY_MODE      # "47005"
IndevoltConfig.WRITE_DISCHARGE_LIMIT  # "1142"
# ... and more

# Read registers
IndevoltConfig.READ_ENERGY_MODE       # "7101"
IndevoltConfig.READ_DISCHARGE_LIMIT   # "6105"
# ... and more

IndevoltRealtimeAction

Action values for real-time control mode, used with SET_REALTIME_ACTION.

from indevolt_api import IndevoltRealtimeAction

IndevoltRealtimeAction.STOP       # "0"
IndevoltRealtimeAction.CHARGE     # "1"
IndevoltRealtimeAction.DISCHARGE  # "2"

IndevoltEnergyMode

Energy mode values for IndevoltConfig.WRITE_ENERGY_MODE.

from indevolt_api import IndevoltEnergyMode

IndevoltEnergyMode.OUTDOOR_PORTABLE
IndevoltEnergyMode.SELF_CONSUMED_PRIORITIZED
IndevoltEnergyMode.REAL_TIME_CONTROL
IndevoltEnergyMode.CHARGE_DISCHARGE_SCHEDULE

IndevoltBattery, IndevoltSystem, IndevoltGrid, IndevoltSolar

Register key enums for reading battery, system-level, grid, and solar data points.

from indevolt_api import IndevoltBattery, IndevoltSystem, IndevoltGrid, IndevoltSolar

data = await api.fetch_data([
    IndevoltBattery.SOC,
    IndevoltBattery.POWER,
    IndevoltSystem.OUTPUT_POWER,
    IndevoltGrid.METER_POWER_GEN2,
    IndevoltSolar.DC_INPUT_POWER_1,
])

SET_REALTIME_ACTION

The register key used to send real-time charge/discharge commands to the device.

from indevolt_api import SET_REALTIME_ACTION

await api.set_data(SET_REALTIME_ACTION, [IndevoltRealtimeAction.CHARGE, 700, 80])

Discovery Constants

All discovery-related constants are importable from indevolt_api.

Constant Value Description
ACTIVE_DISCOVERY_PORT 10000 Local port devices respond to
ACTIVE_DISCOVERY_MESSAGE b"AT+IGDEVICEIP" Broadcast payload
ACTIVE_DISCOVERY_TIMEOUT 5.0 Default async_discover timeout (seconds)
PASSIVE_DISCOVERY_PORT 8099 Port to bind for passive listening
PASSIVE_DISCOVERY_MAGIC b"BCF-D" Magic prefix of device broadcasts
PASSIVE_DISCOVERY_BIND_ADDR "0.0.0.0" Bind address for the passive listener

DEVICE_LIMITS

Dictionary of per-generation device limits used by check_charge_limits() and check_discharge_limits().

from indevolt_api import DEVICE_LIMITS

print(DEVICE_LIMITS[1])  # {'max_discharge_power': 800, 'max_charge_power': 1200, 'minimum_soc': 5}
print(DEVICE_LIMITS[2])  # {'max_discharge_power': 2400, 'max_charge_power': 2400, 'minimum_soc': 5}

Requirements

  • Python 3.11+
  • aiohttp >= 3.9.0

License

MIT License - see LICENSE file for details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

indevolt_api-1.8.7.tar.gz (16.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

indevolt_api-1.8.7-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

File details

Details for the file indevolt_api-1.8.7.tar.gz.

File metadata

  • Download URL: indevolt_api-1.8.7.tar.gz
  • Upload date:
  • Size: 16.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for indevolt_api-1.8.7.tar.gz
Algorithm Hash digest
SHA256 edb3c3c8ffbf9bef27a062af7b9297bc076e0cb48afdae7f2e8fda687ddf3706
MD5 9cdb559aa37bc41edea37f2a1c52ae70
BLAKE2b-256 0b07c8164159bacc888101dda09ef3c01ac720d66c9468fbfe1ee5deb714f5e8

See more details on using hashes here.

File details

Details for the file indevolt_api-1.8.7-py3-none-any.whl.

File metadata

  • Download URL: indevolt_api-1.8.7-py3-none-any.whl
  • Upload date:
  • Size: 14.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for indevolt_api-1.8.7-py3-none-any.whl
Algorithm Hash digest
SHA256 5be201758848e1c838b4525c85d0e6ea0d71c377cec1efe5c44d945af21bed13
MD5 abb853bbacd7511a79dff675efd43670
BLAKE2b-256 41ebf49e4b1392b51ce9b35db5ed5acd5a49e543ca6476786b032375376c7545

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.8.7 This release

2 files

1.8.6

2 files

1.8.5

2 files

1.8.4

2 files

1.8.3

2 files

1.8.2

2 files

1.8.1

2 files

1.8.0

2 files

1.7.2

2 files

1.7.1

2 files

1.7.0

2 files

1.6.5

2 files

1.6.4

2 files

1.6.3

2 files

1.6.2

2 files

1.6.1

2 files

1.6.0

2 files

1.5.0

2 files

1.4.3

2 files

1.4.2

2 files

1.4.1

2 files

1.4.0

2 files

1.3.1

2 files

1.3.0

2 files

1.2.3

2 files

1.2.2

2 files

1.2.1

2 files

1.2.0

2 files

1.1.2

2 files

1.1.0

2 files

1.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page