Skip to main content

spacepak-modbus

Read and control SpacePak Solstice Inverter Extreme (ILAHP) air-to-water heat pumps over Modbus, as typed Python objects rather than register numbers.

The library maps the ILAHP register set onto modbus-connection's device model: you hand it a ModbusUnit, call async_update(), and read sub-systems as attributes. It owns no connection and no I/O policy. The caller does.

Status

This is a working reference pulled from one real installation, shared in case it is useful. It is not an actively maintained package: there is no commitment to triage issues or pull requests, or to track future modbus-connection changes. Forks are welcome, and so is anyone who wants to take it over.

Supported devices

The SpacePak Solstice Inverter Extreme (ILAHP) range, per the register map in its installation manual (ILHP2-0423). It has been exercised against two units behind a Modbus TCP gateway. Other SpacePak models are untested.

The unit speaks Modbus RTU at 9600 baud, 8N1, on its RS-485 port. Every register this library reads is a holding register.

What it covers

Component Registers What
controls 1011-1012, 1028, 1158-1165 Power, operating mode, whether the hot water function is enabled, heating and cooling targets, the unit's own setpoint limits
status 2011-2032 Running, current mode (defrost included), load outputs, compressor hours
measurements 2042-2072 Water, air, coil and refrigerant temperatures, currents, voltages, compressor frequency
faults 2081-2090 The nine failure registers, decoded into named faults

The manual lists room temperature (T09, 2058) and water flow (T39, 2077) as not used on this unit, so they are left out. The hot water tank temperature (T08) only has a sensor behind it when the unit's hot water function is enabled (controls.hot_water_enabled).

The installer parameters (registers 1013-1270: compressor frequency curves, EEV steps, defrost timing, weather compensation, timers) are deliberately left out. Writing those by mistake would misconfigure the heat pump, not just misreport it.

Every temperature is stored in tenths of a degree Celsius, whatever the unit's display is set to show, so every temperature here is in °C.

Usage

import asyncio

from modbus_connection import ModbusTcpParams
from modbus_connection.tmodbus import ModbusConnection
from spacepak_modbus import IlahpHeatPump


async def main() -> None:
    connection = ModbusConnection(ModbusTcpParams(host="192.168.1.50", port=502))
    try:
        heat_pump = IlahpHeatPump(connection.for_unit(1))
        await heat_pump.async_update()

        print("Running:", heat_pump.status.running, heat_pump.status.unit_mode)
        print("Outlet:", heat_pump.measurements.outlet_temperature, "°C")
        print("Outdoor:", heat_pump.measurements.ambient_temperature, "°C")
        print("Target:", heat_pump.controls.heating_target_temperature, "°C")
        print("Faults:", [fault.key for fault in heat_pump.faults.active_faults])
    finally:
        await connection.close()


asyncio.run(main())

Readings and settings refresh separately

  • async_update_readings() reads status, measurements and faults: what the unit measures and reports.
  • async_update_settings() reads controls: registers that change when something writes them. Run one after a write to read back what took effect.
  • async_update() does both, in one report.

Each returns an UpdateReport. A component whose block the unit refuses keeps its previous values and is listed in report.failed; the rest still refresh. A dead link (ModbusConnectionError) raises, and so does a timeout before anything has answered.

Writing

await heat_pump.controls.write("power_on", True)
await heat_pump.controls.write("heating_target_temperature", 45.0)
await heat_pump.async_update_settings()

Setpoint writes are checked against the widest range each register accepts. The unit applies its own tighter limits too, which it reports as min_heating_setpoint/max_heating_setpoint and min_cooling_setpoint/max_cooling_setpoint. Clamp to those before writing.

The operating mode is read-only here. The mode register's values depend on whether the installer enabled cooling (parameter H05), and a wrong mode can leave a hydronic system without heat.

Faults

The unit reports faults as nine bitmask registers. faults.active_faults decodes them into Fault entries, each with its failure register, bit, a stable snake_case key, and a description. FAULTS is the full table of the bits the manual names. A set bit the manual does not name (failure register 3 has no published bit table) is still reported, as failure_<n>_bit_<b>, so nothing is hidden. faults.any_fault is true when any register has a bit set.

Checking a real heat pump

script/query.py reads one unit once and prints every value, which is the quickest way to see whether a unit is reachable and addressed correctly:

uv run script/query.py 192.168.1.50 --port 502 --unit 1
uv run script/query.py socket://192.168.1.50:8899 --transport serial --unit 1
uv run script/query.py /dev/ttyUSB0 --transport serial --baudrate 9600 --unit 1 --raw

--raw adds every register it read, undecoded, which is what an issue about a wrong value should quote. A raw dump loads straight into modbus-connection's mock with load_raw(), so it can become a test.

Development

uv sync
uv run pytest
uv run ruff check . && uv run ruff format --check . && uv run mypy

The tests run against modbus-connection's in-memory mock backend. No hardware is needed.

Source

The register map comes from the SpacePak ILAHP Installation, Operation & Maintenance manual (ILHP2-0423), Modbus section. Addresses, scales and the enum and bit tables follow it. On live units:

  • 1011, 1158 and 1159 have been read and written.
  • Every register this library reads has been read from two units and checked against the values an existing integration reported for the same units.
  • The operating mode (1012) and the setpoint limits (1162-1165) read as expected. The limits are whatever the installer configured, so they differ from the manual's defaults.

License

Unlicense: public domain.

Metadata

Release files for spacepak-modbus 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for spacepak-modbus 0.1.0
File Size Uploaded Provenance Actions
spacepak_modbus-0.1.0.tar.gz 43.5 kB With provenance Details for spacepak_modbus-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for spacepak-modbus 0.1.0
File Interpreter ABI Platform Actions
spacepak_modbus-0.1.0-py3-none-any.whl Python 3 none any Details for spacepak_modbus-0.1.0-py3-none-any.whl

Total release size: 56.8 kB

Release files / spacepak_modbus-0.1.0.tar.gz

Download URL spacepak_modbus-0.1.0.tar.gz
Size 43.5 kB
Tags Source
SHA-256 checksum
How to use checksums
a18a36d95fbf3651f77c177e15e87f1027136b741af2b9295381d53065513b7f
BLAKE2b-256 checksum
How to use checksums
d2164ee64b85d1c916ffd5e87b31b7c9144d2f61a2670ca37d14bb4aed4bd592
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / spacepak_modbus-0.1.0-py3-none-any.whl

Download URL spacepak_modbus-0.1.0-py3-none-any.whl
Size 13.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b3cd88b85f576f7893efc07b40a4a3d762007f474516d58f298ba5e64f07418d
BLAKE2b-256 checksum
How to use checksums
e5847abd36c9820a72c55ba017eb4adcb8bb89c799455a9a1e33743eecab643e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Security for spacepak-modbus 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for spacepak_modbus-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page