Skip to main content

Local control for choochoo based thermostats

Project description

steamloop

CI Status Documentation Status Test coverage percentage

uv Ruff pre-commit

PyPI Version Supported Python versions License


Async Python library for local control of thermostat devices over mTLS (port 7878).

Installation

pip install steamloop

CLI

Pairing

Put the thermostat in pairing mode (Menu > Settings > Network > Advanced Setup > Remote Connection > Pair), then:

steamloop 192.168.1.100 --pair

This saves a pairing file in the current directory with the secret key.

Monitoring

steamloop 192.168.1.100

If already paired, you can pass the secret key directly to skip the pairing file:

steamloop 192.168.1.100 --key YOUR_SECRET_KEY

Interactive commands: status, heat <temp>, cool <temp>, mode <off|auto|cool|heat>, fan <auto|on|circulate>, eheat <on|off>, help.

Library Usage

import asyncio
from steamloop import ThermostatConnection, ZoneMode, FanMode

async def main():
    conn = ThermostatConnection(
        "192.168.1.100",
        secret_key="your-secret-key-from-pairing",
    )
    async with conn:
        # State is populated automatically from thermostat events
        for zone_id, zone in conn.state.zones.items():
            print(f"{zone.name}: {zone.indoor_temperature}°F")

        # Send commands (sync — no await needed)
        conn.set_temperature_setpoint("1", heat_setpoint="72")
        conn.set_zone_mode("1", ZoneMode.COOL)
        conn.set_fan_mode(FanMode.AUTO)

asyncio.run(main())

Pairing Programmatically

pair() returns the secret key directly — store it however you like:

from steamloop import ThermostatConnection

async def pair(ip: str) -> str:
    conn = ThermostatConnection(ip, secret_key="")
    try:
        await conn.connect()
        ssk = await conn.pair()
        return ssk["secret_key"]  # store in a database, config entry, etc.
    finally:
        await conn.disconnect()

Or use the built-in file helpers to save/load pairing data to disk:

from steamloop import ThermostatConnection, save_pairing, load_pairing

# Save after pairing
await save_pairing(ip, {
    "secret_key": secret_key,
    "device_type": "automation",
    "device_id": "module",
})

# Load later
pairing = await load_pairing(ip)
conn = ThermostatConnection(ip, secret_key=pairing["secret_key"])

Event Callbacks

def on_event(msg):
    print("Received:", msg)

remove = conn.add_event_callback(on_event)
# later: remove() to unregister

Home Assistant Integration

Key design points for using steamloop in a Home Assistant integration:

  • Commands are syncset_zone_mode(), set_fan_mode(), set_temperature_setpoint() use transport.write() internally, so they won't block the event loop. No await needed.
  • State is always fresh — the asyncio.Protocol receives events via data_received() and updates conn.state automatically. Just read properties directly.
  • Auto-reconnect — after calling start_background_tasks(), the connection automatically reconnects with exponential backoff (5s, 10s, 20s, ... up to 5 min).
  • Event callbacks — use add_event_callback() to trigger async_write_ha_state() when the thermostat pushes updates.
  • Multi-zone — create one ClimateEntity per conn.state.zones entry. Zones are populated automatically after login.

API Reference

ThermostatConnection(ip, port=7878, *, secret_key, cert_set=None, device_type="automation", device_id="module")

Method Async Description
connect() yes Establish mTLS connection
login() yes Authenticate with secret key
pair() yes Pair and receive secret key
start_background_tasks() no Start heartbeat + auto-reconnect
disconnect() yes Close connection and stop tasks
set_temperature_setpoint(zone_id, *, heat_setpoint, cool_setpoint, hold_type) no Set zone temperature
set_zone_mode(zone_id, mode) no Set zone HVAC mode
set_fan_mode(mode) no Set fan mode
set_emergency_heat(enabled) no Toggle emergency heat
add_event_callback(fn) no Register event listener (returns unregister callable)

Supports async with for automatic connect/login/disconnect:

async with ThermostatConnection(ip, secret_key=key) as conn:
    ...  # connected, logged in, background tasks running
# automatically disconnected

Enums

  • ZoneModeOFF, AUTO, COOL, HEAT
  • FanModeAUTO, ALWAYS_ON, CIRCULATE
  • HoldTypeUNDEFINED, MANUAL, SCHEDULE, HOLD

State

  • conn.state.zonesdict[str, Zone] with temperature, setpoints, mode per zone
  • conn.state.fan_mode — current FanMode
  • conn.state.supported_modeslist[ZoneMode]
  • conn.state.emergency_heat / relative_humidity / cooling_active / heating_active

Contributors

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!

Credits

Copier

This package was created with Copier and the browniebroke/pypackage-template project template.

Project details


Download files

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

Source Distribution

steamloop-1.2.0.tar.gz (42.5 kB view details)

Uploaded Source

Built Distribution

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

steamloop-1.2.0-py3-none-any.whl (29.3 kB view details)

Uploaded Python 3

File details

Details for the file steamloop-1.2.0.tar.gz.

File metadata

  • Download URL: steamloop-1.2.0.tar.gz
  • Upload date:
  • Size: 42.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for steamloop-1.2.0.tar.gz
Algorithm Hash digest
SHA256 7e9c35c74527ac0b95de799aa1f3ecad3c32c975a98850f20719a1a9df2ad33a
MD5 e2d5dc18b4984a29ef3953110304c874
BLAKE2b-256 3c54724961f4872d4a442d4386dc1f5e1436861b22e24aa90d650ce1af118223

See more details on using hashes here.

Provenance

The following attestation bundles were made for steamloop-1.2.0.tar.gz:

Publisher: ci.yml on hvaclibs/steamloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file steamloop-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: steamloop-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 29.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for steamloop-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 27d35b408f0987c026aa0a138a1da0222f7044d89335ef52913a14ceae3aa0d6
MD5 0aa8cad19b96b6b4950d0a2bf5b32960
BLAKE2b-256 6b01558dd642b7d0e913f4bd5bf2a9780693c0ce59319455796b79a27647ca9b

See more details on using hashes here.

Provenance

The following attestation bundles were made for steamloop-1.2.0-py3-none-any.whl:

Publisher: ci.yml on hvaclibs/steamloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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