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()readsstatus,measurementsandfaults: what the unit measures and reports.async_update_settings()readscontrols: 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.