Skip to content

Repository files navigation

GivEnergy Modbus

GivEnergy

pypi python CI codecov license Ruff

A Python library for communicating with GivEnergy inverters via Modbus TCP on a local network, with no dependency on the GivEnergy Cloud. Inspired by and originally built on pymodbus, it now provides its own asyncio-based framer, decoder and PDUs specific to the GivEnergy implementation.

⚠️ This project makes no representations as to its completeness or correctness. You use it at your own risk — if your inverter mysteriously explodes because you accidentally set the BOOMTIME register or you consume a MWh of electricity doing SOC calibration: you really are on your own. We make every effort to prevent you from shooting yourself in the foot, so as long as you use the client and its exposed methods, you should be perfectly safe.

Features

  • Reading all registers and decoding them into their representative datatypes
  • Writing data to holding registers that are deemed to be safe to set configuration on the inverter

Supported hardware

The library speaks to GivEnergy plants through the wifi/ethernet data adapter ("dongle") using their Modbus-TCP variant. Support is evidence-based: validated means the decode is pinned by golden-master tests against committed wire captures from real hardware in tests/fixtures/captures/.

Plant type Status Notes
Single-phase hybrid (Gen 1/Gen 3) ✅ Validated The reference platform. Battery-less (AC/PV-only) configurations detect correctly too.
AC-coupled single-phase (AC / AC Gen 3) ✅ Validated Including EMS-managed operation.
All-in-One (AIO) ✅ Validated HV battery stack decoded per-module (BCU + BMS + per-BMU cell data at their own device addresses).
Three-phase hybrid (HV) ✅ Validated Per-phase electrical data; HV stacks with per-cell BMU decode.
EMS controller ✅ Validated Plant-level rollup, managed-inverter enumeration, EMS-specific register banks.
Gateway (Gen 1) ✅ Validated Fronting parallel AIOs; battery data register-embedded per-AIO in the gateway rollup.
Single-phase hybrid Gen 2 ⚠️ Untested Modelled, but no Gen 2 unit has ever been observed — a field report from an EA-prefix owner would settle the device type code.
Polar, commercial variants (AIO/EMS Commercial), 3-phase AC ⚠️ Untested Model codes known; decode unverified against real hardware.

Batteries: LV packs (chained at their own device addresses), the LV BCU, HV stacks (BCU/BMS/per-module BMUs), and gateway-embedded per-AIO data are all modelled. Energy meters (EM115/EM418, up to eight) decode including per-phase power factor and apparent power.

Firmware oddities are documented as we find them in hardware & firmware quirks. If your hardware isn't listed — or misbehaves — an issue with a short wire capture is the fastest route to support: every ✅ above exists because someone contributed one.

How to use

The client is async. Use it inside an asyncio event loop; commands are plain functions that return request lists which you send via one_shot_command or execute:

import asyncio
from givenergy_modbus.client.client import Client
from givenergy_modbus.client import commands
from givenergy_modbus.model import TimeSlot
from givenergy_modbus.model.inverter import Model

async def main():
    client = Client(host="192.168.99.99", port=8899)
    await client.connect()

    # Detect the topology once, then read the config banks (needed for slot_map)
    await client.detect()
    await client.load_config()
    plant = client.plant

    # Write configuration to the device
    await client.one_shot_command(commands.set_charge_target_enabled(80))
    # set a charging slot from 00:30 to 04:30; slot_map selects correct registers for this model
    await client.one_shot_command(
        commands.set_charge_slot(1, TimeSlot.from_components(0, 30, 4, 30), plant.inverter.slot_map)
    )
    # set the inverter to charge from excess solar and discharge to meet demand
    await client.one_shot_command(commands.set_mode_dynamic())

    assert plant.inverter_serial_number == 'SA1234G567'
    assert plant.inverter.model == Model.HYBRID
    assert plant.inverter.enable_charge_target
    assert plant.inverter.charge_slot_1 == TimeSlot.from_components(0, 30, 4, 30)
    assert plant.inverter.model_dump() == {
        'serial_number': 'SA1234G567',
        'device_type_code': '3001',
        'charge_slot_1': TimeSlot.from_components(0, 30, 4, 30),
        ...
    }
    assert plant.inverter.model_dump_json() == '{"serial_number": "SA1234G567", "device_type_code": "3001", ...'

    assert plant.batteries[0].serial_number == 'BG1234G567'
    assert plant.batteries[0].v_cell_01 == 3.117
    assert plant.batteries[0].model_dump() == {
        'bms_firmware_version': 3005,
        'cap_design': 160.0,
        ...
    }
    assert plant.batteries[0].model_dump_json() == '{"serial_number": "BG1234G567", "v_cell_01": 3.117, ...'

    await client.close()

asyncio.run(main())

Credits

This package was created with Cookiecutter and the waynerv/cookiecutter-pypackage project template.

About

A python library to access GivEnergy inverters via Modbus TCP on a local network, with no dependency on the GivEnergy Cloud.

Topics

Resources

Contributing

Security policy

Stars

27 stars

Watchers

6 watching

Forks

Releases

Used by

Contributors

Languages